{"record":{"id":"7771d3f4565ee9a6","repo":"sipeed/picoclaw","slug":"no-default-api-base-for-provider-q","errorCode":null,"errorMessage":"No default API base for provider %q","messagePattern":"No default API base for provider %q","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/models.go","lineNumber":676,"sourceCode":"\tif !providers.IsModelProviderFetchable(req.Provider) {\n\t\thttp.Error(w, fmt.Sprintf(\"provider %q does not support model listing\", req.Provider), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tapiKey := strings.TrimSpace(req.APIKey)\n\tapiBase := strings.TrimSpace(req.APIBase)\n\n\tif apiKey == \"\" && req.ModelIndex != nil {\n\t\tif stored := h.lookupStoredAPIKey(*req.ModelIndex, req.Provider, apiBase); stored != \"\" {\n\t\t\tapiKey = stored\n\t\t}\n\t}\n\n\tif apiBase == \"\" {\n\t\tapiBase = providers.DefaultAPIBaseForProtocol(req.Provider)\n\t}\n\tif apiBase == \"\" {\n\t\thttp.Error(w, fmt.Sprintf(\"No default API base for provider %q\", req.Provider), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)\n\tdefer cancel()\n\n\tmodels, err := fetchUpstreamModels(ctx, req.Provider, apiBase, apiKey)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to fetch models: %v\", err), http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Auto-save fetched models to catalog\n\tcatalogModels := make([]CatalogModel, len(models))\n\tfor i, m := range models {\n\t\tcatalogModels[i] = CatalogModel{ID: m.ID, OwnedBy: m.OwnedBy}\n\t}\n\tif saveErr := SaveCatalog(req.Provider, apiBase, apiKey, catalogModels); saveErr != nil {","sourceCodeStart":658,"sourceCodeEnd":694,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L658-L694","documentation":"Returned by POST /api/models/fetch when api_base was not supplied AND providers.DefaultAPIBaseForProtocol(provider) returns \"\" for that provider. The handler refuses to guess an endpoint: without an api_base it cannot build the upstream /models URL, so it returns 400 naming the provider.","triggerScenarios":"POST /api/models/fetch with {\"provider\": \"custom-openai-compatible\"} (a provider type with no well-known default base) and no api_base; api_base sent as \"\" or only whitespace after trimming; a proxy provider that requires an explicit endpoint.","commonSituations":"Self-hosted LLM setups (ollama, vllm, llama.cpp) behind a custom provider id; frontend omits the api_base field for providers where it assumed a default exists; provider added via config with empty api_base.","solutions":["Send an explicit api_base: {\"provider\": \"...\", \"api_base\": \"http://localhost:11434/v1\"}","Verify the api_base value is non-empty after trimming — whitespace-only fails","Confirm the provider id is the one that actually has a default base (e.g. plain \"openai\"), not a custom alias"],"exampleFix":"// before\nbody: JSON.stringify({ provider: 'custom', api_key: key })\n\n// after\nbody: JSON.stringify({ provider: 'custom', api_base: 'http://localhost:11434/v1', api_key: key })","handlingStrategy":"validation","validationCode":"function effectiveApiBase(provider: string, apiBase: string | undefined, defaults: Record<string,string>): string {\n  const base = (apiBase ?? '').trim();\n  if (base) return base;\n  const def = defaults[provider];\n  if (!def) throw new Error(`no default API base for ${provider} — api_base is required`);\n  return def;\n}","typeGuard":"function hasApiBase(v: unknown): v is { api_base: string } {\n  return typeof v === 'object' && v !== null && typeof (v as any).api_base === 'string' && (v as any).api_base.trim() !== '';\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/models/fetch', {...});\n  if (res.status === 400 && (await res.text()).includes('No default API base')) {\n    /* mark api_base required in the form for this provider and block submit */\n  }\n} catch (e) { /* network */ }","preventionTips":["Treat api_base as required for custom/self-hosted provider ids","Trim api_base before sending — whitespace-only fails the check","Include the /v1 suffix (or provider-correct prefix) in saved bases for OpenAI-compatible servers"],"tags":["http","validation","api-base","provider"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}