{"record":{"id":"9fb697019fff017a","repo":"ChatGPTNextWeb/NextChat","slug":"oops-there-s-an-issue-let-s-fix-it-1","errorCode":null,"errorMessage":"😆 Oops, there's an issue. Let's fix it:\n     \\ 1️⃣ New here? [Click to start chatting now 🚀](${SAAS_CHAT_UTM_URL})\n     \\ 2️⃣ Using a private setup? [Click here](/#/auth) to enter your key 🔑\n     \\ 3️⃣ Want to use your own OpenAI resources? [Click here](/#/settings) to change settings ⚙️\n     ","messagePattern":"😆 Oops, there's an issue\\. Let's fix it:\n     \\\\ 1️⃣ New here\\? \\[Click to start chatting now 🚀\\]\\((.+?)\\)\n     \\\\ 2️⃣ Using a private setup\\? \\[Click here\\]\\(/#/auth\\) to enter your key 🔑\n     \\\\ 3️⃣ Want to use your own OpenAI resources\\? \\[Click here\\]\\(/#/settings\\) to change settings ⚙️\n     ","errorType":"exception","errorClass":"Error","httpStatus":401,"severity":"error","filePath":"app/client/platforms/openai.ts","lineNumber":460,"sourceCode":"\n    const [used, subs] = await Promise.all([\n      fetch(\n        this.path(\n          `${OpenaiPath.UsagePath}?start_date=${startDate}&end_date=${endDate}`,\n        ),\n        {\n          method: \"GET\",\n          headers: getHeaders(),\n        },\n      ),\n      fetch(this.path(OpenaiPath.SubsPath), {\n        method: \"GET\",\n        headers: getHeaders(),\n      }),\n    ]);\n\n    if (used.status === 401) {\n      throw new Error(Locale.Error.Unauthorized);\n    }\n\n    if (!used.ok || !subs.ok) {\n      throw new Error(\"Failed to query usage from openai\");\n    }\n\n    const response = (await used.json()) as {\n      total_usage?: number;\n      error?: {\n        type: string;\n        message: string;\n      };\n    };\n\n    const total = (await subs.json()) as {\n      hard_limit_usd?: number;\n    };\n","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/ChatGPTNextWeb/NextChat/blob/defdcdb55d850cd12c4c657eb83729fd66e215c0/app/client/platforms/openai.ts#L442-L478","documentation":"This is the localized Locale.Error.Unauthorized string, thrown at openai.ts:460 when the HTTP status of the OpenAI 'dashboard/billing/usage' request is exactly 401. It is the user-facing 'enter your key / change settings' message that NextChat surfaces whenever the API rejects the request as unauthenticated. The 401 short-circuits before the generic 'failed to query usage' check on line 463-464.","triggerScenarios":"The usage() method issues GET requests to OpenaiPath.UsagePath ('dashboard/billing/usage') and OpenaiPath.SubsPath ('dashboard/billing/subscription'). If the 'used' response has status === 401 — invalid/expired API key, empty OPENAI_API_KEY, wrong base URL pointing at a proxy that returns 401, or a key without billing scope — this error is thrown immediately.","commonSituations":"First-time NextChat setup with no key configured; key rotated/expired on the OpenAI dashboard; user switched from the SaaS endpoint to a custom proxy but did not re-enter the key; Azure OpenAI base URL used against the openai.com billing path; key has access to chat completions but the proxy strips billing endpoints and returns 401.","solutions":["Open Settings (/#/settings) and confirm the OpenAI API Key field is filled with a valid sk- key that is active on platform.openai.com.","If using a custom base URL / proxy, verify it accepts the same Authorization header and does not return 401 for the dashboard/billing/* paths.","If using Azure, switch ServiceProvider to Azure in settings so the Azure-specific paths are used instead of the OpenAI billing paths.","Regenerate the key on platform.openai.com if it was revoked, then re-paste it and reload.","If the deployment cannot reach api.openai.com at all, use the SaaS chat endpoint linked in the error message."],"exampleFix":"// before\nif (used.status === 401) {\n  throw new Error(Locale.Error.Unauthorized);\n}\n\n// after — distinguish auth failure from missing billing endpoint so the real cause is visible\nif (used.status === 401 || subs.status === 401) {\n  throw new Error(Locale.Error.Unauthorized);\n}\nif (used.status === 404 || subs.status === 404) {\n  // billing endpoints were removed from api.openai.com; treat as 'usage unavailable'\n  return { used: undefined, total: undefined } as LLMUsage;\n}","handlingStrategy":"validation","validationCode":"import { useAccessStore } from \"@/app/client\";\n\nfunction hasOpenaiKey(): boolean {\n  const access = useAccessStore.getState();\n  const key = access.openaiApiKey;\n  return typeof key === \"string\" && key.trim().startsWith(\"sk-\") && key.length > 20;\n}\n\n// before calling usage()\nif (!hasOpenaiKey()) {\n  throw new Error(Locale.Error.Unauthorized); // or route user to settings\n}","typeGuard":"function isOpenaiAuthFailure(res: Response): boolean {\n  return res.status === 401;\n}","tryCatchPattern":"try {\n  const usage = await openai.usage();\n} catch (e) {\n  if (e instanceof Error && e.message === Locale.Error.Unauthorized) {\n    // route to /#/auth or /#/settings\n  } else {\n    throw e;\n  }\n}","preventionTips":["Validate the key shape (sk- prefix, non-empty) before issuing any request.","Keep base URL and key together in settings so a proxy switch also re-prompts for the key.","Use Azure-specific paths when ServiceProvider is Azure to avoid hitting openai.com billing routes."],"tags":["openai","auth","billing","api-key","i18n"],"backgroundTag":null,"analyzedSha":"defdcdb55d850cd12c4c657eb83729fd66e215c0","analyzedAt":"2026-08-12T09:57:26.489Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}