santifer/career-ops · error · Error
No free models found in API response
Error message
No free models found in API response
What it means
Thrown by loadFreeModels() after the models API call succeeds but the response contains zero models whose prompt AND completion pricing are both "0" — i.e. OpenRouter returned a valid JSON payload with no free models matching the filter. The library treats an empty filtered list as fatal because the runner's whole strategy is rotating through free models.
Solutions
- Inspect the raw response: `curl https://openrouter.ai/api/v1/models | jq '.data[0].pricing'` and compare with the filter's expectations.
- Check for OpenRouter API changelog notes about pricing field format; update the filter if '0' became 0 or pricing nested differently.
- Verify OPENROUTER_MODELS_URL points at the official /api/v1/models endpoint.
- If genuinely no free models exist on OpenRouter anymore, switch the runner to paid models with a key holding credits.
- Add logging of `data.data.length` before filtering to distinguish 'API returned nothing' from 'filter matched nothing'.
Example fix
// before
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');
// after
const all = data.data ?? [];
const list = all.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 (API returned ${all.length} models; pricing schema may have changed). Sample pricing: ${JSON.stringify(all[0]?.pricing ?? null)}`);
} Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch('https://openrouter.ai/api/v1/models');
const { data } = await res.json();
const free = (data ?? []).filter(m => String(m.pricing?.prompt) === '0' && String(m.pricing?.completion) === '0');
if (free.length === 0) console.warn('No free models match current pricing schema — inspect data[0].pricing:', data?.[0]?.pricing); Type guard
function isFreeModel(m) {
return m != null
&& typeof m.id === 'string'
&& m.pricing != null
&& String(m.pricing.prompt) === '0'
&& String(m.pricing.completion) === '0';
} Prevention
- Log the raw count of models before filtering so schema drift is visible.
- Subscribe to OpenRouter changelog/announcements for pricing-format changes.
- Write a small contract test against the live models endpoint.
- Pin a fallback paid-model path if the free filter ever yields nothing.
When it happens
Trigger: OpenRouter responds 200 with a models array that, after filtering `String(p.prompt) === '0' && String(p.completion) === '0'`, yields an empty list: pricing schema changed, `data.data` shape changed, or the account/plan context filters out all free models.
Common situations: OpenRouter changed the pricing representation (e.g. numbers instead of strings, or per-token pricing moved); a breaking API version change; scraping an unofficial endpoint URL that now serves a different payload; running against a mocked/proxied API returning `{data: []}`.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- agentic-jobs: parsed 0 jobs from the API — the response…
- All active models failed. Last error
- ${data.error.message}
- Empty response
- himalayas: unexpected API response - expected
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/233e2ccd36a83952.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:134
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;
}
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(View on GitHub (pinned to aac998c7ed)