thedotmack/claude-mem · error

OpenRouter API error

Error message

OpenRouter API error: ${response.status} - ${errorText}

What it means

Thrown in queryOpenRouterMultiTurn when the OpenRouter HTTP response has a non-OK status. The raw status code and response body are wrapped as the cause and passed to classifyOpenRouterError, which maps it to a typed error (rate limit, auth, upstream provider, etc.). It signals the chat-completion request failed at the HTTP level before any model output was produced.

Solutions

  1. Read the classified error (status/body) to identify the HTTP cause; fix auth or billing first for 401/402
  2. Retry with backoff on 429/5xx; the code already captures request-id headers for retry dedup
  3. Verify the model slug exists on OpenRouter (GET /api/v1/models)
  4. Log errorText from the cause for the upstream provider's detailed message

Example fix

// before
const res = await fetch(OPENROUTER_URL, { headers: { Authorization: `Bearer ${key}` } });
// after
if (!key?.startsWith('sk-or-')) throw new Error('OPENROUTER_API_KEY missing/invalid before call');
const res = await fetch(OPENROUTER_URL, { headers: { Authorization: `Bearer ${key}` } });
if (res.status === 429) await backoffRetry(() => callOpenRouter(messages));
Defensive patterns

Strategy: retry

Validate before calling

if (!process.env.OPENROUTER_API_KEY) throw new Error('OPENROUTER_API_KEY not set');
// model slug sanity
if (!/^[-a-zA-Z0-9/_.]+$/.test(model)) throw new Error(`suspicious model slug: ${model}`);

Type guard

function isRateLimitOrServer(e: unknown): e is { status: number } {
  return typeof e === 'object' && e !== null && 'status' in e && typeof (e as any).status === 'number' && ((e as any).status === 429 || (e as any).status >= 500);
}

Try / catch

try {
  return await provider.query(messages);
} catch (e) {
  if (isRetryable(e)) { await sleep(backoff()); return await provider.query(messages); }
  throw e;
}

Prevention

When it happens

Trigger: Any OpenRouter /chat/completions request where response.ok is false: 401 invalid API key, 402 insufficient credits, 429 rate limited, 5xx from OpenRouter or an upstream model provider, 400 malformed request body.

Common situations: Expired or missing OPENROUTER_API_KEY, exhausted account credits, model slug typo (e.g. wrong vendor prefix), free-tier rate limits hit, upstream provider outage for the chosen model.

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 thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/dc5567bd917bd37b. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker/OpenRouterProvider.ts:554

    const data = await withRetry<OpenRouterResponse>(async (attemptSignal) => {
      let response: Response;
      try {
        response = await this.fetchChatCompletion(apiUrl, apiKey, model, fallbackModels, messages, siteUrl, appName, priorRequestId, attemptSignal, plainText);
      } catch (networkError: unknown) {
        const err = networkError instanceof Error ? networkError : new Error(String(networkError));
        throw classifyOpenRouterError({ cause: err });
      }

      const requestId = response.headers.get('x-request-id') ?? response.headers.get('x-openrouter-request-id');
      if (requestId) {
        priorRequestId = requestId;
      } else {
        logger.debug('SDK', 'OpenRouter response missing request-id header; retry dedup is best-effort');
      }

      if (!response.ok) {
        const errorText = await response.text();
        throw classifyOpenRouterError({
          status: response.status,
          bodyText: errorText,
          headers: response.headers,
          cause: new Error(`OpenRouter API error: ${response.status} - ${errorText}`),
          ...(requestId ? { requestId } : {}),
        });
      }

      const responseData = await response.json() as OpenRouterResponse;

      if (responseData.error) {
        // Per OpenRouter spec, errors can come in 200 responses too.
        throw classifyOpenRouterError({
          status: response.status,
          bodyText: JSON.stringify(responseData),
          headers: response.headers,
          cause: new Error(`OpenRouter API error: ${responseData.error.code} - ${responseData.error.message}`),
          ...(requestId ? { requestId } : {}),

View on GitHub (pinned to d8bc9755e7)