santifer/career-ops · error · Error
[models] Failed to fetch free model list
Error message
[models] Failed to fetch free model list: ${reason}. Check that your API key is valid and that network access to OpenRouter is available. What it means
loadFreeModels() in openrouter-runner.mjs GETs https://openrouter.ai/api/v1/models with an Authorization: Bearer header and needs at least one model whose pricing.prompt AND pricing.completion are both the string "0". This variant of the wrapper (openrouter-runner.mjs:140) fires when OPENROUTER_API_KEY IS set but something in that fetch/filter threw: the original reason is prefixed to the message — 'HTTP 401' (invalid/revoked key), 'HTTP 429'/'HTTP 5xx' (provider side), a network/DNS TypeError from fetch, or 'No free models found in API response' when the catalog exposed zero zero-priced models.
Solutions
- Reproduce with the helper: `node openrouter-runner.mjs models` — same code path, clearer output
- Check the key: curl -s -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/models -o /dev/null -w '%{http_code}' — 200 means key fine, look at network next; 401/403 means the key is bad, create a new one at openrouter.ai/keys
- Verify egress: curl https://openrouter.ai from the same shell/container; check HTTP(S)_PROXY env if a proxy intercepts
- If the prefixed reason says 'No free models found in API response', retry later — the catalog momentarily exposed no zero-priced models
- For 5xx reasons, wait and retry; nothing is wrong locally
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: key present AND models endpoint reachable before starting a run
async function preflightOpenRouter() {
const key = process.env.OPENROUTER_API_KEY;
if (!key) throw new Error('OPENROUTER_API_KEY missing — see .env setup');
const r = await fetch('https://openrouter.ai/api/v1/models', {
headers: { Authorization: `Bearer ${key}` },
});
if (!r.ok) throw new Error(`OpenRouter preflight failed: HTTP ${r.status}`);
const { data } = await r.json();
const free = (data ?? []).filter(m => String(m.pricing?.prompt) === '0' && String(m.pricing?.completion) === '0');
if (free.length === 0) throw new Error('No free models in catalog right now');
return free.map(m => m.id);
} Try / catch
try {
const models = await loadFreeModels();
} catch (e) {
if (e.message.startsWith('[models] Failed to fetch free model list')) {
// branch on the suffix: key-variant vs network-variant
console.error(e.message.includes('OPENROUTER_API_KEY is not set')
? 'Fix .env first (cp .env.example .env)'
: 'OpenRouter unreachable or key rejected — check network/key');
}
} Prevention
- Run `node openrouter-runner.mjs models` once after any key rotation to confirm auth works
- Keep the OpenRouter key in the repo-root .env only; rotate it if it ever appears in logs
- In CI, add the preflight check as a separate step so failures are labeled auth vs network clearly
When it happens
Trigger: node openrouter-runner.mjs <mode> (or the `models` helper command) with a key that is malformed, revoked, or has no access; egress to openrouter.ai blocked by firewall/proxy; OpenRouter outage; a catalog snapshot where every model has non-zero pricing (filter at line 116-119 empties and line 122 throws).
Common situations: Key revoked/rotated on openrouter.ai after it leaked into a dotfile; corporate proxy blocking openrouter.ai; OpenRouter briefly deprecating all :free variants; DNS failure in CI containers.
Related errors
- All active models failed. Last error
- arbeitsagentur: all keyword request(s) failed
- archive render skipped — Greenhouse API unreachable
- ${data.error.message}
- Empty response
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/da337590ce05ab7e.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:140
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;
}
freeModels = list.sort((a, b) => {
const diff = priorityOf(a) - priorityOf(b);
return diff !== 0 ? diff : a.localeCompare(b);
});
console.log(`[models] ${freeModels.length} free models loaded from OpenRouter API.`);
} catch (e) {
const reason = e instanceof Error ? e.message : String(e);
const hasKey = Boolean(process.env.OPENROUTER_API_KEY);
throw new Error(
`[models] Failed to fetch free model list: ${reason}. ` +
(hasKey ? 'Check that your API key is valid and that network access to OpenRouter is available.'
: 'OPENROUTER_API_KEY is not set — copy .env.example to .env and add your key.')
);
}
return freeModels;
}
// List and exit (helper command)
async function cmdModels() {
const models = await loadFreeModels();
console.log(`\nFree models available on OpenRouter (${models.length} total):\n`);
models.forEach((id, i) => console.log(` ${String(i + 1).padStart(2)}. ${id}`));
console.log('');
}
// ---------------------------------------------------------------------------View on GitHub (pinned to 60398d6549)