KeepRouter API errors: fix 401, 402, 403, and 429

Updated: 2026-09-27.

Start with error.code. A 401 requires checking the API key; a 403 may require a different model permission or account action; a 402 requires checking available credits and reserved spend. For a 429, inspect the specific code and Retry-After before retrying. Adding credits does not change a key's model allowlist.

ResponseCheck firstRecovery step
401 · invalid_api_keyKey validity and the secret loaded by the running processConfigure a valid KeepRouter key in that runtime, then restart or redeploy if required
403 · model_not_allowedWhether this key permits the exact model IDCreate a key with the required model scope, then replace the application's key
402 · insufficient_creditsAvailable balance, output limit, and in-flight reservationsLower the request's output limit, wait for a reservation to release, or add credits
429 · key, concurrency, or rate limitThe exact code, window, and retry headerFollow that code's recovery step; a spending window and a concurrency limit need different waits

For a paid model, follow the free-to-paid key setup. The API cost calculator estimates a workload at published model rates; it does not show your account balance, key limits, or in-flight reservations. When changing gateways, use the OpenRouter migration checklist to check the key, base URL, and model ID together.

Before retrying a migrated application, check its credential-free configuration with the browser-local API migration checker. It checks configuration, not account balance or credential validity.

KeepRouter's OpenAI-compatible routes return an OpenAI-style JSON body, plus an x-should-retry response header. The Anthropic-compatible Messages route uses its own error envelope.

{
  "error": {
    "message": "Insufficient credits. Top up at keeprouter.com/console/credits",
    "type": "invalid_request_error",
    "code": "insufficient_credits",
    "retryable": false,
    "doc_url": "https://keeprouter.com/docs/errors#insufficient_credits"
  }
}

retryable (and the x-should-retry header) tells you whether the same request can succeed on retry: true means retry after any Retry-After; false means fix something first.

invalid_api_key

401 · not retryable. The API key is missing, disabled, or expired. Create or re-enable a key in the console.

account_suspended

403 · not retryable. Your account is suspended. Email support@keeprouter.com.

insufficient_credits

402 · not retryable as-is. Your available credits cannot cover this request's maximum estimated cost, or are reserved by requests already in flight. Lower the output-token limit, wait for an in-flight request to finish, or add credits.

model_requires_topup

403 · not retryable. Without a top-up only the published free models are callable. Top up to make the account eligible for paid models, then confirm that the key permits the requested model and the balance covers the request. Topping up does not expand an existing key's model allowlist. Alternatively, choose a published free model permitted by the key.

model_not_allowed

403 · not retryable. This API key's model allowlist doesn't include the requested model. Adding credits alone cannot fix it.

  1. Copy the exact model ID from the model catalog. In the keys console, create a new key whose model scope includes it. For a paid-model test, a name such as paid-test helps identify its usage; the name itself grants no permissions.
  2. Save the new key in the secret store used by the application or set KEEPROUTER_KEY in the process that runs the request. Restart the process or redeploy if it reads secrets at startup. Changing a variable in another terminal does not update a running application.
  3. Send one request with a small supported output limit and inspect its response and console usage. For paid models, also confirm account eligibility and sufficient balance. Keep the old key until the new configuration works, then revoke it if it is no longer needed.

See the free-to-paid walkthrough for the complete setup. Never print either key while checking the runtime configuration.

invalid_max_tokens

400 · not retryable as-is. The request's output-token limit must be a finite positive integer. Correct max_tokens, max_completion_tokens, or max_output_tokens (whichever the endpoint uses) before retrying.

invalid_stream

400 · not retryable as-is. stream must be the JSON boolean true or false, not a string or number. Correct the field before retrying.

invalid_n

400 · not retryable as-is. Chat Completions and image generation accept n only when it is exactly 1. Send separate requests for multiple outputs so each result is admitted and billed independently.

invalid_model

400 · not retryable as-is. model must be a string from 1 to 256 characters with no whitespace. Copy an exact model ID from the current model catalog or GET /v1/models, then retry.

model_not_available

403 · not retryable as-is. The requested model has no route candidate available to this API key. Confirm the model ID, endpoint compatibility, and key allowlist against the current model catalog, or choose another compatible model.

request_body_too_large

413 · not retryable as-is. The JSON request body exceeds the 2 MiB gateway limit. Reduce or split the request before trying again. This limit also applies to chunked bodies without a Content-Length header and to /v1/messages/count_tokens.

pricing_unavailable

503 · not retryable as-is. KeepRouter does not have a complete published price for this model's current route candidates, so the request is stopped before any upstream call or charge. Choose another model or email support@keeprouter.com.

key_limit_reached

429 · retryable after the window or an in-flight request finishes. This request's maximum estimated cost, together with committed and reserved spend, would cross the key's daily or monthly limit. Lower the output-token limit, wait for a reservation to release, raise the key limit, or wait for the period to reset.

concurrency_limit_exceeded

429 · retryable. This account has too many in-flight requests (or its smaller high-cost request pool is full). Wait for one to finish, then retry after Retry-After (currently 5 seconds).

invalid_webhook_url

400 · not retryable as-is. A webhook_url must be an https URL on port 443 with a public DNS hostname. Internal, private, loopback and cloud-metadata destinations are refused, as are URLs carrying credentials or a fragment and URLs on the gateway's own domain — the gateway would be the one making the request, so the destination is validated as a security boundary rather than a convenience. Omit the field to use polling instead. See Video generation.

invalid_duration

400 · not retryable as-is. A video request's duration must be a whole number of seconds inside the range the model accepts. The upstream charges as soon as it ACCEPTS a task and does not refund one it later fails on a bad parameter, so an out-of-range length is refused here instead of being billed and lost. The message names the model's own range: Wan 2.7 accepts 2 to 15 seconds, HappyHorse 1.0 3 to 15, MiniMax H3 4 to 15, and the PixVerse tiers 2 or more. Omit duration to use the model's default.

invalid_task_id

400 · not retryable as-is. A video task id may contain only letters, digits, ., -, _, and :, up to 200 characters. Use the id returned by POST /v1/video/generations unchanged.

upstream_account_unavailable

503 · retryable later. The upstream account this model routes through cannot serve requests right now — in practice, its provider balance is exhausted. KeepRouter replaces the provider's own message with this one because that message describes the operator's account rather than your request. The operator is notified automatically; use another model, or retry once the account is topped up. Nothing was charged to you for the failed attempt.

task_expired

404 · not retryable as-is. KeepRouter tracks a submitted video task for 24 hours, matching the provider's signed-download window, then stops. Past that the task id can no longer be resolved — the clip URL it would have returned has expired too. Re-submit the render if you still need the clip.

task_not_found

404 · not retryable as-is. No video task with this id is reachable for this account. Either the id was never issued, or it belongs to a different account. A task is readable by the account that submitted it — or, for an admin-created key, by that key alone. Re-submit the render if it is no longer needed.

resolution_not_priced

400 · not retryable as-is. This video model id is published for one resolution tier only, and the request asked for a different one — the upstream charges more for the higher tier, so the gateway will not sell it at the lower price. Omit resolution, send the tier in the message (for example 720p), or use the model id published for the resolution you want (for example wan2.7-t2v-1080p).

rate_limit_exceeded

429 · retryable. You exceeded the requests-per-minute limit for your tier. Back off and retry after Retry-After. Topped-up accounts get higher limits. ;