{"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":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":360,"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 := httpclient.NewUserAgentClient(nil).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":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/openai.go#L342-L378","documentation":"The normal error path for ListAvailableModelsWithContext when GET /models returns non-2xx with a readable body. The message includes the HTTP status code and the trimmed response body (the provider's error JSON/text), giving the user the upstream's actual reason.","triggerScenarios":"401 invalid/expired API key; 403 region/org blocked; 404 wrong apiBaseURL (missing /v1 or wrong host); 422 provider rejects the request shape; 429 rate limit; 5xx provider outage.","commonSituations":"Typo in API key; apiBaseURL set to the chat endpoint instead of the base; using an OpenAI key against a non-compatible provider; free-tier quota exhausted; provider maintenance.","solutions":["For 401/403: re-enter the API key in settings and verify it works in the provider's console.","For 404: set apiBaseURL to the provider base (e.g. https://api.openai.com/v1), not the chat/completions path.","For 429: wait and lower request rate; check quota/billing.","For 5xx: check the provider status page and retry later."],"exampleFix":"# before: apiBaseURL = https://api.openai.com/v1/chat/completions\n#   -> 404 list models HTTP 404: ...\n# after:  apiBaseURL = https://api.openai.com/v1","handlingStrategy":"try-catch","validationCode":"if !strings.Contains(apiBaseURL, \"/v1\") && isOpenAICompatible {\n    log.Warn(\"apiBaseURL usually needs a /v1 suffix for the /models endpoint\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := util.ListAvailableModelsWithContext(key, base, 30); err != nil {\n    // err.Error() already contains status + provider reason; surface it\n    return err\n}","preventionTips":["Store API keys in settings, not in code","Confirm apiBaseURL ends at the version root (e.g. /v1)","Keep a known-working key in the provider console to cross-check"],"tags":["ai","openai","network","go","models","auth"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}