paperclipai/paperclip · warning

Could not load OpenRouter models. Retry or enter a model ID…

Error message

Could not load OpenRouter models. Retry or enter a model ID manually.

What it means

listOpenRouterModels fetches OpenRouter's public model catalog (https://openrouter.ai/api/v1/models) with a 10s timeout. If the HTTP response status is not ok, it throws 'Could not load OpenRouter models. Retry or enter a model ID manually.' The message is user-facing: the catalog could not be fetched, but a model ID can still be entered manually.

Solutions

  1. Retry the request (call listOpenRouterModels(true) to force refresh) — the error message itself suggests retrying.
  2. Enter a model ID manually in the agent configuration instead of picking from the catalog.
  3. Check network egress to https://openrouter.ai/api/v1/models (curl it to confirm reachability).
  4. If OpenRouter is rate limiting, wait and retry; note successful results are cached for 60 seconds.

Example fix

// before
const models = await listOpenRouterModels();
// after
let models;
try {
  models = await listOpenRouterModels();
} catch {
  models = []; // fall back to manual model ID entry in the UI
}
Defensive patterns

Strategy: retry

Validate before calling

const res = await fetch("https://openrouter.ai/api/v1/models", { signal: AbortSignal.timeout(10_000) });
if (!res.ok) console.warn("openrouter catalog unreachable, status", res.status);

Try / catch

let models = [];
for (let attempt = 0; attempt < 3 && models.length === 0; attempt++) {
  try { models = await listOpenRouterModels(attempt > 0); }
  catch (e) { if (!e.message.includes("OpenRouter models")) throw e; await sleep(2 ** attempt * 500); }
}

Prevention

When it happens

Trigger: Calling the OpenRouter models endpoint (via agentRoutes) when the upstream returns a non-2xx status: network outage, OpenRouter 429/5xx, blocked egress, or DNS failure with a non-ok response.

Common situations: Corporate firewall blocking openrouter.ai; OpenRouter outage or rate limiting; request exceeding the 10-second AbortSignal timeout in an environment with slow egress.

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


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/4bd2cdcc91cccf52. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/openrouter-models.ts:12

import type { AdapterModel } from "@paperclipai/adapter-utils";

let cached: { until: number; models: AdapterModel[] } | undefined;
let pending: Promise<AdapterModel[]> | undefined;

/** OpenRouter's public catalog does not require access to anyone's credentials. */
export async function listOpenRouterModels(refresh = false): Promise<AdapterModel[]> {
  if (!refresh && cached && cached.until > Date.now()) return cached.models;
  if (pending) return pending;
  pending = (async () => {
    const response = await fetch("https://openrouter.ai/api/v1/models", { signal: AbortSignal.timeout(10_000) });
    if (!response.ok) throw new Error("Could not load OpenRouter models. Retry or enter a model ID manually.");
    const body = await response.json() as { data?: Array<{ id?: unknown; name?: unknown }> };
    if (!Array.isArray(body.data)) throw new Error("OpenRouter returned an invalid model catalog.");
    const models = body.data.flatMap(model => typeof model.id === "string" && model.id.includes("/")
      ? [{ id: `openrouter/${model.id}`, label: typeof model.name === "string" ? model.name : model.id }]
      : []).sort((a, b) => a.label.localeCompare(b.label));
    cached = { until: Date.now() + 60_000, models };
    return models;
  })();
  try { return await pending; } finally { pending = undefined; }
}

View on GitHub (pinned to 3f1d897a7c)