santifer/career-ops · error · Error
HTTP
Error message
HTTP ${resp.status} What it means
Thrown by loadFreeModels() in openrouter-runner.mjs when the OpenRouter models endpoint (`OPENROUTER_MODELS_URL`) returns a non-ok HTTP status. The function caches the free-model list, so this only fires on the first fetch of the session (or after cache invalidation). The raw status code is surfaced verbatim so callers can distinguish auth problems (401/402) from server errors (5xx).
Solutions
- Read the status: 401/402 means the key — verify OPENROUTER_API_KEY in .env matches a valid key at openrouter.ai/keys (no quotes/whitespace).
- On 429, wait and retry; the list request is rate-limited per account.
- On 5xx, check https://openrouter.statuspage.io or retry later — it is server-side.
- Confirm network/proxy access to openrouter.ai (try `curl -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/models`).
- The wrapped catch at line 152 will rethrow with a hint (key vs no key) — use it to diagnose.
Example fix
// before
const resp = await fetch(OPENROUTER_MODELS_URL, {
headers: { 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}` }
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
// after
const resp = await fetch(OPENROUTER_MODELS_URL, {
headers: { 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}` }
});
if (!resp.ok) {
if (resp.status === 401 || resp.status === 402) throw new Error(`OpenRouter auth failed (${resp.status}) — check OPENROUTER_API_KEY`);
if (resp.status === 429) throw new Error('OpenRouter rate limit hit — retry shortly');
throw new Error(`OpenRouter models endpoint returned HTTP ${resp.status}`);
} Defensive patterns
Strategy: retry
Validate before calling
const key = process.env.OPENROUTER_API_KEY;
if (!key) throw new Error('OPENROUTER_API_KEY is not set');
const pre = await fetch('https://openrouter.ai/api/v1/models', { headers: { Authorization: `Bearer ${key}` } });
if (!pre.ok) throw new Error(`OpenRouter models endpoint unhealthy: HTTP ${pre.status}`); Try / catch
try {
models = await loadFreeModels();
} catch (err) {
if (/HTTP 429/.test(err.message)) {
await sleep(30_000);
models = await loadFreeModels();
} else {
console.error('Model list load failed:', err.message);
process.exit(1);
}
} Prevention
- Rotate/validate your OpenRouter key periodically from openrouter.ai/keys.
- Retry 429/5xx with exponential backoff before failing.
- Monitor https://openrouter.statuspage.io during batch runs.
- Cache a known-good model list on disk to survive transient API failures.
When it happens
Trigger: First call that triggers loadFreeModels() when GET to the OpenRouter models API returns 401 (bad API key), 402 (credits/billing gate), 429 (rate limit), or 5xx from OpenRouter.
Common situations: Expired or revoked OPENROUTER_API_KEY in .env; typo'd key with stray whitespace; OpenRouter incident/maintenance returning 503; hitting rate limits after many rapid evaluations.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- [models] Failed to fetch free model list
- All active models failed. Last error
- HTTP
- HTTP
- [models] Failed to fetch free model list
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/75550f5f54ca8315.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:123
// Models that failed permanently (403, timeout, persistent 429) — never retry
const blacklistedModels = new Set(loadPersistedBlacklist());
if (blacklistedModels.size > 0) {
console.log(`[blacklist] Loaded ${blacklistedModels.size} pre-blacklisted model(s) from disk.`);
}
// 429 failure count per model — auto-blacklist after 3 consecutive 429s
const rateLimitCounts = {};
// ---------------------------------------------------------------------------
// Fetch free models from OpenRouter API
// ---------------------------------------------------------------------------
async function loadFreeModels() {
if (freeModels !== null) return freeModels;
try {
const resp = await fetch(OPENROUTER_MODELS_URL, {
headers: { 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}` }
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
// A model is free when its prompt and completion pricing are both "0"
const list = (data.data ?? [])
.filter(m => {
const p = m.pricing ?? {};
return String(p.prompt) === '0' && String(p.completion) === '0';
})
.map(m => m.id);
if (list.length === 0) throw new Error('No free models found in API response');
// Sort by provider priority; within the same provider sort alphabetically
function providerOf(id) { return id.split('/')[0]; }
function priorityOf(id) {
const idx = PROVIDER_PRIORITY.indexOf(providerOf(id));
return idx === -1 ? PROVIDER_PRIORITY.length : idx;
}View on GitHub (pinned to aac998c7ed)