Chat
Create a chat completion
OpenAI-compatible chat completions. Set stream: true to receive server-sent events.
Only one choice is returned: n, if sent, must be 1.
An error before the first token is an ordinary HTTP error. An error after streaming has started cannot change the 200; it is sent as a final event, data: {"error": {...}} (same envelope as every other error), and the stream ends.
https://api.router.africa/v1/chat/completionsAuthorizations
Authorizationbearer (API key (sk-ra-...))headerrequiredSend your key as Authorization: Bearer YOUR_KEY. Never put a key in the URL or the body: the request is refused with key_not_in_header.
Headers
Idempotency-Keystringrouter.africa extension, not part of OpenAI's API. A client-chosen string (for example a UUID). A repeat of a finished request with the same key and the same API key returns the first result instead of running (and charging) again. A repeat while the first is still running returns 409 request_in_progress. Without this header an identical body is treated the same way.
Body application/json
modelstringrequiredA model id from GET /v1/models, for example openai/gpt-4.1-mini.
messagesChatMessage[]required2 child fields
rolestringrequiredcontentstring | any[] | nullText, or an array of content parts.
streambooleandefault: falsemax_tokensintegerOutput cap. Defaults to the model's default. A value above the model's ceiling is silently lowered to the ceiling; it is not an error.
n1temperaturenumbertop_pnumberstopstring | string[]presence_penaltynumberfrequency_penaltynumberseedintegerresponse_formatobjecttoolsobject[]tool_choicestring | objectparallel_tool_callsbooleanlogprobsbooleantop_logprobsintegeruserstringResponse
200
The completion, or a stream of chunks when stream is true.
Response headers: x-request-id, x-idempotent-replay. Include x-request-id when you contact support.
idstringrequiredobject"chat.completion"requiredcreatedintegermodelstringrequiredchoicesobject[]required3 child fields
indexintegerrequiredmessageChatMessagerequired2 child fields
rolestringrequiredcontentstring | any[] | nullText, or an array of content parts.
finish_reasonstring | nullrequiredusageUsage3 child fields
prompt_tokensintegerrequiredcompletion_tokensintegerrequiredtotal_tokensintegerrequiredStreaming
Server-sent events. Each data: line is one JSON ChatCompletionChunk (or, last, an error envelope), then data: [DONE].
Event: ChatCompletionChunk
idstringrequiredobject"chat.completion.chunk"requiredcreatedintegermodelstringrequiredchoicesobject[]required3 child fields
indexintegerrequireddeltaobjectrequiredfinish_reasonstring | nullusageUsage | null1 child field
UsageUsage3 child fields
prompt_tokensintegerrequiredcompletion_tokensintegerrequiredtotal_tokensintegerrequiredEvent: Error
errorobjectrequired3 child fields
codeintegerrequiredThe HTTP status, as a number.
messagestringrequiredSafe to show to a user.
metadataobjectrequired3 child fields
error_typestringrequiredThe portable category. Branch on this for retry logic.
reasonstringrequiredThe specific cause. Branch on this for user-facing handling.
modelstringOn model_not_found, the model id that was sent.
data: {"id":"chatcmpl-0192f0e0","object":"chat.completion.chunk","created":1767225600,"model":"openai/gpt-4.1-mini","choices":[{"index":0,"delta":{"role":"assistant","content":"Habari"},"finish_reason":null}]}
data: {"id":"chatcmpl-0192f0e0","object":"chat.completion.chunk","created":1767225600,"model":"openai/gpt-4.1-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":3,"total_tokens":15}}
data: [DONE]Errors
Errors share one body shape. Branch on error.metadata.error_type for retry logic and on error.metadata.reason for what to show your user. The full table is in Errors.
400invalid_body, key_not_in_header, context_length_exceeded
The request is malformed or fails validation.
{
"error": {
"code": 400,
"message": "Request body is invalid: `messages` is required.",
"metadata": {
"error_type": "invalid_request",
"reason": "invalid_body"
}
}
}{
"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"
}
}
}{
"error": {
"code": 400,
"message": "The request is too long for `openai/gpt-4.1-mini`'s context window. Shorten the input and retry.",
"metadata": {
"error_type": "context_length_exceeded",
"reason": "context_length_exceeded"
}
}
}401missing_api_key, invalid_api_key
The API key is missing or not valid. One answer for unknown, malformed, revoked, expired and pending keys.
{
"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"
}
}
}{
"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"
}
}
}402insufficient_credits, key_spend_cap_exceeded
Not enough credits, or the key's spend cap is reached. Do not retry until the cause is fixed.
{
"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"
}
}
}{
"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"
}
}
}403account_suspended, content_policy_violation
The account is suspended, or the provider refused the content.
{
"error": {
"code": 403,
"message": "This account is suspended. Contact support.",
"metadata": {
"error_type": "permission_denied",
"reason": "account_suspended"
}
}
}{
"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"
}
}
}404model_not_found, route_not_found
The model is unknown, off or not allowed for this key (one answer for all three), or the path does not exist.
{
"error": {
"code": 404,
"message": "The model `acme/unknown-model` does not exist or you do not have access to it.",
"metadata": {
"error_type": "not_found",
"reason": "model_not_found",
"model": "acme/unknown-model"
}
}
}{
"error": {
"code": 404,
"message": "No such endpoint. Check the URL and method.",
"metadata": {
"error_type": "not_found",
"reason": "route_not_found"
}
}
}408upstream_timeout
The model provider took too long, after retries and fallbacks.
{
"error": {
"code": 408,
"message": "The model provider took too long to respond.",
"metadata": {
"error_type": "timeout",
"reason": "upstream_timeout"
}
}
}409request_in_progress
An identical request (same body, or same Idempotency-Key) is still running. Wait and retry; the first request's result is returned.
{
"error": {
"code": 409,
"message": "An identical request is already in progress.",
"metadata": {
"error_type": "invalid_request",
"reason": "request_in_progress"
}
}
}413payload_too_large
The request body is over 16 MiB.
{
"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"
}
}
}429rate_limited, concurrency_limited, edge_rate_limited
Rate limit reached. Back off and honour Retry-After. No x-ratelimit-* headers are sent.
{
"error": {
"code": 429,
"message": "Rate limit reached for this API key. Retry after 20 seconds.",
"metadata": {
"error_type": "rate_limit_exceeded",
"reason": "rate_limited"
}
}
}{
"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"
}
}
}{
"error": {
"code": 429,
"message": "Too many requests from this address. Retry after 60 seconds.",
"metadata": {
"error_type": "rate_limit_exceeded",
"reason": "edge_rate_limited"
}
}
}500internal_error
A fault on our side. Retry once, then contact support with the request id.
{
"error": {
"code": 500,
"message": "Something went wrong on our side. Reference: 0192f0e0-7b1a-7c3e-9d4f-5a6b7c8d9e0f.",
"metadata": {
"error_type": "server",
"reason": "internal_error"
}
}
}502provider_error
The model provider returned an error after fallbacks.
{
"error": {
"code": 502,
"message": "The model provider returned an error.",
"metadata": {
"error_type": "provider_unavailable",
"reason": "provider_error"
}
}
}503service_unavailable
Temporarily unavailable; refused before any model was called.
{
"error": {
"code": 503,
"message": "The service is temporarily unavailable.",
"metadata": {
"error_type": "server",
"reason": "service_unavailable"
}
}
}529provider_overloaded
The model provider is overloaded and fallbacks are exhausted.
{
"error": {
"code": 529,
"message": "The model provider is overloaded. Retry shortly.",
"metadata": {
"error_type": "provider_overloaded",
"reason": "provider_overloaded"
}
}
}