Skip to content
Get API key
On this page

Errors

The error response format, and every error the API returns with its status, reason, an example and what to do.

Errors use one JSON shape. Branch on the HTTP status for retry logic and on metadata.reason for the specific cause. The exception is POST /v1/messages, which uses Anthropic’s format. See Errors on POST /v1/messages.

{
  "error": {
    "code": 402,
    "message": "This workspace doesn't have enough credits for this request. Add credits at https://app.router.africa.",
    "metadata": {
      "error_type": "payment_required",
      "reason": "insufficient_credits"
    }
  }
}
Field Meaning
error.code The HTTP status, as a number.
error.message A message you can show to a person. It never contains a full key, a provider name or any of your prompt.
error.metadata.error_type The category of the error.
error.metadata.reason The specific cause. Messages can change, so match on this, not on the message.

The OpenAI and Anthropic SDKs choose the exception from the HTTP status and read error.message, so they work unchanged. error.code is a number, not a string.

Headers

Handle an error

curl -i https://api.router.africa/v1/chat/completions \
-H "Authorization: Bearer $ROUTER_AFRICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "openai/does-not-exist", "messages": [{"role": "user", "content": "Hello"}]}'

# HTTP/2 404
# x-request-id: req_8f2c91

Errors while streaming

An error before the first token is a normal error response. An error after the stream has started can’t change the 200 status. It arrives as a final data: {"error": {…}} event with the same body, and then the stream ends. On POST /v1/messages it is an event: error event in Anthropic’s format. See Streaming.

Retrying

Retry 408, 429, 500, 502, 503 and 529 with backoff. For 409, wait, then retry. Don’t retry 400, 401, 402, 403, 404 or 413 without changing something first. See Idempotency and retries.

A max_tokens (or max_output_tokens) above the model’s limit is lowered to the limit. It isn’t an error.

Error reference

400 invalid_body

Body isn’t valid JSON or fails validation. Error type: invalid_request. What to do: Fix the request. Don’t retry.

{
  "error": {
    "code": 400,
    "message": "Request body is invalid: messages must be a non-empty array.",
    "metadata": {
      "error_type": "invalid_request",
      "reason": "invalid_body"
    }
  }
}

400 key_not_in_header

A key was sent in the URL or the body. Error type: invalid_request. What to do: Move the key to the header. Rotate it.

{
  "error": {
    "code": 400,
    "message": "Send your API key in the Authorization header, not the URL or body.",
    "metadata": {
      "error_type": "invalid_request",
      "reason": "key_not_in_header"
    }
  }
}

400 context_length_exceeded

The prompt is too long for the model. Error type: context_length_exceeded. What to do: Shorten the input.

{
  "error": {
    "code": 400,
    "message": "The request is too long for openai/gpt-4o-mini's context window. Shorten the input and retry.",
    "metadata": {
      "error_type": "context_length_exceeded",
      "reason": "context_length_exceeded"
    }
  }
}

401 missing_api_key

No Authorization header, or it isn’t Bearer. On POST /v1/messages, also no x-api-key. Error type: authentication. What to do: Add the header.

{
  "error": {
    "code": 401,
    "message": "You didn't provide an API key. Send it as `Authorization: Bearer YOUR_KEY`.",
    "metadata": {
      "error_type": "authentication",
      "reason": "missing_api_key"
    }
  }
}

401 invalid_api_key

Key unknown, malformed, revoked, expired or not yet active. Error type: authentication. What to do: Use a valid key. Don’t retry.

{
  "error": {
    "code": 401,
    "message": "Incorrect API key provided: sk-ra-…abcd. Check your keys at https://app.router.africa.",
    "metadata": {
      "error_type": "authentication",
      "reason": "invalid_api_key"
    }
  }
}

402 insufficient_credits

Available credits are too low for the request. Error type: payment_required. What to do: Add credits. Don’t retry until then.

{
  "error": {
    "code": 402,
    "message": "This workspace doesn't have enough credits for this request. Add credits at https://app.router.africa.",
    "metadata": {
      "error_type": "payment_required",
      "reason": "insufficient_credits"
    }
  }
}

402 key_spend_cap_exceeded

The key reached its spend cap. Error type: payment_required. What to do: Raise the cap, wait for it to reset, or use another key.

{
  "error": {
    "code": 402,
    "message": "This API key has reached its spend limit. Raise the limit or use another key.",
    "metadata": {
      "error_type": "payment_required",
      "reason": "key_spend_cap_exceeded"
    }
  }
}

403 account_suspended

The account or workspace is suspended. Error type: permission_denied. What to do: Contact support. Don’t retry.

{
  "error": {
    "code": 403,
    "message": "This account is suspended. Contact support.",
    "metadata": {
      "error_type": "permission_denied",
      "reason": "account_suspended"
    }
  }
}

403 content_policy_violation

The model provider refused the content. Error type: content_policy_violation. What to do: Change the content.

{
  "error": {
    "code": 403,
    "message": "The request was blocked by the model provider's content policy.",
    "metadata": {
      "error_type": "content_policy_violation",
      "reason": "content_policy_violation"
    }
  }
}

404 model_not_found

The model is unknown, switched off, or not allowed for the key. Error type: not_found. What to do: Check the model id and the key’s allowed models.

{
  "error": {
    "code": 404,
    "message": "The model openai/gpt-5-nano does not exist or you do not have access to it.",
    "metadata": {
      "error_type": "not_found",
      "reason": "model_not_found",
      "model": "openai/gpt-5-nano"
    }
  }
}

404 route_not_found

Unknown path or method. Error type: not_found. What to do: Check the endpoint and method.

{
  "error": {
    "code": 404,
    "message": "No such endpoint. Check the URL and method.",
    "metadata": {
      "error_type": "not_found",
      "reason": "route_not_found"
    }
  }
}

409 request_in_progress

An identical request is still running. Error type: invalid_request. What to do: Wait, then retry. You get the first request’s result.

{
  "error": {
    "code": 409,
    "message": "An identical request is already in progress.",
    "metadata": {
      "error_type": "invalid_request",
      "reason": "request_in_progress"
    }
  }
}

413 payload_too_large

The body is over 16 MiB. Error type: payload_too_large. What to do: Send less.

{
  "error": {
    "code": 413,
    "message": "The request body is too large. Send less than 16 MiB.",
    "metadata": {
      "error_type": "payload_too_large",
      "reason": "payload_too_large"
    }
  }
}

429 rate_limited

The key’s rate limit was reached. Error type: rate_limit_exceeded. What to do: Back off. Honour Retry-After.

{
  "error": {
    "code": 429,
    "message": "Rate limit reached for this API key. Retry after 12 seconds.",
    "metadata": {
      "error_type": "rate_limit_exceeded",
      "reason": "rate_limited"
    }
  }
}

429 concurrency_limited

Your account has too many requests running at once. Error type: rate_limit_exceeded. What to do: Back off. Honour Retry-After.

{
  "error": {
    "code": 429,
    "message": "Too many requests are running at once for this account. Wait for some to finish, then retry.",
    "metadata": {
      "error_type": "rate_limit_exceeded",
      "reason": "concurrency_limited"
    }
  }
}

429 edge_rate_limited

Too many requests from one IP address, or too many rejected keys from it. Error type: rate_limit_exceeded. What to do: Back off. Honour Retry-After.

{
  "error": {
    "code": 429,
    "message": "Too many requests from this address. Retry after 30 seconds.",
    "metadata": {
      "error_type": "rate_limit_exceeded",
      "reason": "edge_rate_limited"
    }
  }
}

408 upstream_timeout

The model provider took too long. Error type: timeout. What to do: Retry with backoff.

{
  "error": {
    "code": 408,
    "message": "The model provider took too long to respond.",
    "metadata": {
      "error_type": "timeout",
      "reason": "upstream_timeout"
    }
  }
}

500 internal_error

A problem on our side. Error type: server. What to do: Retry once. Contact support with the request id.

{
  "error": {
    "code": 500,
    "message": "Something went wrong on our side. Reference: req_8f2c91.",
    "metadata": {
      "error_type": "server",
      "reason": "internal_error"
    }
  }
}

502 provider_error

The model provider returned an error. Error type: provider_unavailable. What to do: Retry with backoff.

{
  "error": {
    "code": 502,
    "message": "The model provider returned an error.",
    "metadata": {
      "error_type": "provider_unavailable",
      "reason": "provider_error"
    }
  }
}

503 service_unavailable

The service is temporarily unavailable. No model was called. Error type: server. What to do: Retry with backoff.

{
  "error": {
    "code": 503,
    "message": "The service is temporarily unavailable.",
    "metadata": {
      "error_type": "server",
      "reason": "service_unavailable"
    }
  }
}

529 provider_overloaded

The model provider is overloaded. Error type: provider_overloaded. What to do: Retry with backoff.

{
  "error": {
    "code": 529,
    "message": "The model provider is overloaded. Retry shortly.",
    "metadata": {
      "error_type": "provider_overloaded",
      "reason": "provider_overloaded"
    }
  }
}

Errors on POST /v1/messages

Anthropic’s SDKs parse Anthropic’s error body, so errors on POST /v1/messages (including authentication and route_not_found) use it. error.type is Anthropic’s type, chosen from the HTTP status. error.reason is the specific cause, the same values as metadata.reason above, and per-reason extras such as model sit beside it inside error. The message text is the same. There is no error.code and no metadata. Status codes and headers don’t change.

{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "You didn't provide an API key. Send it as `x-api-key: YOUR_KEY` or `Authorization: Bearer YOUR_KEY`.",
    "reason": "missing_api_key"
  }
}
Status error.type Reasons
400, 409, 413 invalid_request_error invalid_body, key_not_in_header, context_length_exceeded, request_in_progress, payload_too_large
401 authentication_error missing_api_key, invalid_api_key
402 billing_error insufficient_credits, key_spend_cap_exceeded
403 permission_error account_suspended, content_policy_violation
404 not_found_error model_not_found, route_not_found
429 rate_limit_error rate_limited, concurrency_limited, edge_rate_limited. Retry-After is always sent.
408, 500, 502, 503 api_error upstream_timeout, internal_error, provider_error, service_unavailable
529 overloaded_error provider_overloaded. Retry-After is always sent.

After a stream has started, the status is already 200, so the error is a final event: error event in the same format, and then the stream ends.

event: error
data: {"type":"error","error":{"type":"overloaded_error","message":"The model provider is overloaded. Retry shortly.","reason":"provider_overloaded"}}

The 429 errors that come from Cloudflare’s edge rules can’t tell this path apart and keep the standard body above.