{"record":{"id":"808cdbd3512aae00","repo":"Mintplex-Labs/anything-llm","slug":"e-message-808cdb","errorCode":null,"errorMessage":"${e.message}","messagePattern":"\\$\\{e\\.message\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/AiProviders/openRouter/index.js","lineNumber":259,"sourceCode":"  async getChatCompletion(messages = null, { temperature = 0.7, user = null }) {\n    if (!(await this.isValidChatCompletionModel(this.model)))\n      throw new Error(\n        `OpenRouter chat: ${this.model} is not valid for chat completion!`\n      );\n\n    const result = await LLMPerformanceMonitor.measureAsyncFunction(\n      this.openai.chat.completions\n        .create({\n          model: this.model,\n          messages,\n          temperature,\n          // This is an OpenRouter specific option that allows us to get the reasoning text\n          // before the token text.\n          include_reasoning: true,\n          user: user?.id ? `user_${user.id}` : \"\",\n        })\n        .catch((e) => {\n          throw new Error(e.message);\n        })\n    );\n\n    if (\n      !result?.output?.hasOwnProperty(\"choices\") ||\n      result?.output?.choices?.length === 0\n    )\n      throw new Error(\n        `Invalid response body returned from OpenRouter: ${result.output?.error?.message || \"Unknown error\"} ${result.output?.error?.code || \"Unknown code\"}`\n      );\n\n    return {\n      textResponse: this.#parseReasoningFromResponse(result.output.choices[0]),\n      metrics: {\n        prompt_tokens: result.output.usage.prompt_tokens || 0,\n        completion_tokens: result.output.usage.completion_tokens || 0,\n        total_tokens: result.output.usage.total_tokens || 0,\n        outputTps: result.output.usage.completion_tokens / result.duration,","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/526360e320da9d1b36074be5ed64fe76e5bbfbbd/server/utils/AiProviders/openRouter/index.js#L241-L277","documentation":"Re-throws the raw rejection message from the OpenRouter OpenAI-compatible chat.completions.create call. OpenRouter is middleware, so rejections often carry upstream-provider context: 404 model not found (server-side, distinct from the local cache check), 402 payment required / no credits, 429 rate limit, 503 upstream provider down. The catch surfaces only e.message.","triggerScenarios":"OpenRouter returns 404 (model id valid in cache but unavailable server-side), 402 (zero credit balance), 429 (rate limited), 503 (upstream provider error/degraded), or a 400 from a malformed request.","commonSituations":"Free-tier quota exhausted; selected upstream provider is down; credit balance is zero; region-restricted model; rate limit from too many concurrent requests; model id correct in cache but removed server-side since last sync.","solutions":["Read e.message and e.status to classify (402 vs 429 vs 503).","402: top up OpenRouter credits or switch to a free model.","503/502: retry with backoff or pick a different provider for the same model.","429: reduce concurrency / add backoff; 404: re-sync the model catalog (see error 276)."],"exampleFix":"// before\n.catch((e) => { throw new Error(e.message); })\n\n// after - keep status so callers can retry 5xx / back off 429\n.catch((e) => {\n  const err = new Error(e.message);\n  err.status = e.status;\n  err.upstream = e?.error?.code;\n  throw err;\n})","handlingStrategy":"try-catch","validationCode":"const ensureOpenRouterCredits = async (apiKey) => {\n  const res = await fetch('https://openrouter.ai/api/v1/key', {\n    headers: { Authorization: `Bearer ${apiKey}` },\n  });\n  if (!res.ok) throw new Error(`OpenRouter key check failed: HTTP ${res.status}`);\n  const { data } = await res.json();\n  if (Number(data?.limit ?? 0) - Number(data?.usage ?? 0) <= 0)\n    throw new Error('OpenRouter credit balance exhausted');\n};\nawait ensureOpenRouterCredits(process.env.OPENROUTER_API_KEY);","typeGuard":"const isRetryableOpenRouter = (e) =>\n  e?.status === 429 || e?.status === 502 || e?.status === 503;\nconst isCreditError = (e) => e?.status === 402;","tryCatchPattern":"try {\n  return await llm.getChatCompletion(messages, { temperature });\n} catch (e) {\n  if (isRetryableOpenRouter(e)) { await sleep(backoffMs); return retry(); }\n  if (isCreditError(e)) throw new Error('OpenRouter out of credit — top up balance');\n  throw e;\n}","preventionTips":["Branch on e.status to retry 5xx/429 but fail fast on 402 (credits).","Monitor OpenRouter credit balance for the configured key.","Have a fallback provider configured for upstream outages."],"tags":["openrouter","llm-provider","runtime","api-error"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}