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

  1. Inspect the raw response: `curl https://openrouter.ai/api/v1/models | jq '.data[0].pricing'` and compare with the filter's expectations.
  2. Check for OpenRouter API changelog notes about pricing field format; update the filter if '0' became 0 or pricing nested differently.
  3. Verify OPENROUTER_MODELS_URL points at the official /api/v1/models endpoint.
  4. If genuinely no free models exist on OpenRouter anymore, switch the runner to paid models with a key holding credits.
  5. 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

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


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)