{"record":{"id":"a45512e5c8fa82a3","repo":"siyuan-note/siyuan","slug":"list-models-http-d-s","errorCode":null,"errorMessage":"list models HTTP %d: %s","messagePattern":"list models HTTP (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":392,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif apiKey != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\t}\n\tresp, err := newAIProviderHTTPClient(apiBaseURL, headers...).Do(req)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"list models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < http.StatusOK || http.StatusBadRequest <= resp.StatusCode {\n\t\tbody, readErr := io.ReadAll(io.LimitReader(resp.Body, 64*1024))\n\t\tif readErr != nil {\n\t\t\terr = fmt.Errorf(\"list models HTTP %d\", resp.StatusCode)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"list models HTTP %d: %s\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t\t}\n\t\tlogging.LogErrorf(\"list models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\n\tpayload := &availableModelsPayload{}\n\tif err = json.NewDecoder(resp.Body).Decode(payload); err != nil {\n\t\tlogging.LogErrorf(\"decode models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\tfor _, item := range payload.Data {\n\t\tid := strings.TrimSpace(item.ID)\n\t\tif id == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tmodels = append(models, AvailableModel{\n\t\t\tID: id,\n\t\t\tContextLength: firstValidModelContextLength(","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/openai.go#L374-L410","documentation":"ListAvailableModelsWithContext wraps a non-2xx response from the provider's model-list endpoint, including the first 64KB of the response body. This is the primary path for surfacing provider rejections (auth failures, bad endpoints, rate limits) when calling ListAvailableModels or the listModelsContract handler. The body text is the provider's own error payload, so it usually states the exact reason.","triggerScenarios":"Calling ListAvailableModels/ListAvailableModelsWithContext where the HTTP status is < 200 or >= 400, e.g. 401 invalid API key, 404 wrong base URL, 429 rate limited, 500 provider outage.","commonSituations":"Expired or wrong API key in the AI model config; base URL missing or wrongly adding /v1; using an OpenAI-specific endpoint against a provider with a different path; quota exhausted; proxy returning an error page.","solutions":["Read the status and body text in the error: a 401 means fix the API key, 404 means fix the base URL, 429 means back off","Verify the model list endpoint path matches the provider (most OpenAI-compatible providers expect <baseURL>/v1/models)","Test the same request with curl using the configured base URL and key to isolate config vs network","If the body is an HTML error page, the URL is hitting the wrong service (proxy/landing page), not the API"],"exampleFix":"// before\nbaseURL := \"https://api.example.com\" // missing /v1\n// after\nbaseURL := \"https://api.example.com/v1\" // so the request hits <baseURL>/models correctly","handlingStrategy":"try-catch","validationCode":"// before calling: verify key + base URL shape\nif !strings.HasPrefix(apiKey, \"sk-\") { return errors.New(\"API key looks invalid\") }\nif !strings.HasSuffix(strings.TrimRight(baseURL, \"/\"), \"/v1\") { log.Println(\"base URL may need a /v1 suffix\") }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    msg := err.Error()\n    switch {\n    case strings.Contains(msg, \"401\"):\n        return fixAPIKeyError(msg)\n    case strings.Contains(msg, \"429\"):\n        return backoffAndRetry()\n    default:\n        return fmt.Errorf(\"provider rejected model list: %w\", err)\n    }\n}","preventionTips":["Store the base URL with the correct /v1 suffix and validate it on config save","Rotate API keys before expiry and re-test the models endpoint after rotation","Check provider status pages when seeing 5xx bodies"],"tags":["http","ai-provider","api-error"],"backgroundTag":"api-error-response","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}