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
- Read the classified error (status/body) to identify the HTTP cause; fix auth or billing first for 401/402
- Retry with backoff on 429/5xx; the code already captures request-id headers for retry dedup
- Verify the model slug exists on OpenRouter (GET /api/v1/models)
- 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
- Validate the API key and account credits before deployment
- Pin and verify model slugs against GET /api/v1/models
- Implement exponential backoff with jitter for 429/5xx
- Log status + body for every failed call to speed diagnosis
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)