santifer/career-ops · error · Error
${data.error.message}
Error message
${data.error.message} What it means
Thrown when the completions endpoint returns HTTP 200 but the JSON body contains an `error` object — OpenRouter's way of reporting provider-level failures (upstream model errors, moderation, context-length overflow) with a 200 envelope. The library surfaces `data.error.message` verbatim so the OpenRouter-provided explanation is what the developer sees.
Solutions
- Read data.error.message in the thrown message — it identifies the upstream cause.
- If it is a context-length error, shorten the prompt or pick a model with a larger window.
- If the upstream model is failing, retry — the rotation should blacklist it and try another free model.
- If moderation-related, rephrase the prompt.
- Treat 200-with-error as a failure signal in any monitoring; don't count it as success because the HTTP status was OK.
Example fix
// before
if (data.error) throw new Error(data.error.message);
// after
if (data.error) {
const err = new Error(data.error.message ?? JSON.stringify(data.error));
err.code = data.error.code;
err.upstream = true; // rotation logic can blacklist this model
throw err;
} Defensive patterns
Strategy: fallback
Type guard
function hasUpstreamError(data) {
return data != null && typeof data === 'object' && data.error != null;
} Try / catch
try {
result = await callOpenRouter(systemPrompt, userMessage);
} catch (err) {
// rotation already blacklists the model; fall back to a known-good model
result = await callWithFallbackModel(systemPrompt, userMessage);
} Prevention
- Never treat HTTP 200 as success without checking data.error.
- Keep prompt sizes under the smallest free model's context window.
- Fall back to another model on upstream errors instead of failing the whole run.
- Track which models emit upstream errors most and deprioritize them.
When it happens
Trigger: Response is 200 OK, resp.json() succeeds, and `data.error` is truthy — e.g. upstream provider timed out, the model refused/errored, prompt exceeded the model's context window, or content moderation rejected the request.
Common situations: Free-tier model's upstream provider returning errors under load; prompts longer than the model's context limit; provider deprecating a model but still accepting requests that fail downstream.
Related errors
- Empty response
- HTTP
- All active models failed. Last error
- [models] Failed to fetch free model list
- No free models found in API response
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/a72a9a280b591522.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:262
const timerId = setTimeout(() => ctrl.abort(), MODEL_TIMEOUT_MS);
try {
const resp = await fetch(OPENROUTER_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/career-ops-hq/career-ops',
'X-Title': 'career-ops',
},
body,
signal: ctrl.signal,
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);
}
const data = await resp.json();
if (data.error) throw new Error(data.error.message);
const content = data.choices?.[0]?.message?.content ?? '';
if (!content) throw new Error('Empty response');
console.log('OK');
const usage = normalizeOpenAIUsage(data.usage);
return { content, usage };
} catch (e) {
if (e.name === 'AbortError') throw new Error(`Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s`);
throw e;
} finally {
clearTimeout(timerId);
}
}
const models = await loadFreeModels();
let lastError;
if (models.length === 0) {
throw new Error(View on GitHub (pinned to aac998c7ed)