Skip to content
Get API key

OpenAI Responses-compatible endpoint, for clients such as the OpenAI Agents SDK and Codex: point the SDK's base URL at https://api.router.africa/v1 and keep your code. Set stream: true to receive Responses server-sent events.

Supported parameters: model, input, instructions, stream, temperature, max_output_tokens and tools (function tools). A max_output_tokens above the model's ceiling is silently lowered to the ceiling. Anything else is not part of the contract.

Errors use the same envelope as POST /v1/chat/completions. An error after streaming has started cannot change the 200; it is sent as a final data: {"error": {...}} event and the stream ends.

POSThttps://api.router.africa/v1/responses

Authorizations

Authorizationbearer (API key (sk-ra-...))headerrequired

Send 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-Keystring

router.africa extension. Works exactly as on POST /v1/chat/completions.

Max length: 255

Body application/json

modelstringrequired

A model id from GET /v1/models.

inputstring | ResponseInputItem[]required

Text, or an array of input items.

1 child field
variant 1ResponseInputItem[]
3 child fields
typestring
rolestring

Allowed values: systemdeveloperuserassistant

contentstring | any[]

Text, or an array of content parts.

instructionsstring

A system instruction for the model.

streambooleandefault: false
temperaturenumber
max_output_tokensinteger

Output 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.

Greater than 0

toolsobject[]

Function tools.

5 child fields
type"function"required
namestringrequired
descriptionstring
parametersobject
strictboolean | null

Response

200

The response, or a stream of events when stream is true.

Response headers: x-request-id, x-idempotent-replay. Include x-request-id when you contact support.

idstringrequired
object"response"required
created_atintegerrequired

Unix time, in seconds.

statusstringrequired

Allowed values: completedfailedin_progressincomplete

modelstringrequired
outputResponseOutputItem[]required
5 child fields
typestringrequired
idstring
statusstring
rolestring
contentobject[]
usageResponseUsage
3 child fields
input_tokensintegerrequired
output_tokensintegerrequired
total_tokensintegerrequired
instructionsstring | null
max_output_tokensinteger | null
incomplete_detailsobject | null

Streaming

Server-sent events in the Responses format: each event has an event: line naming its type and a data: line with one JSON ResponseStreamEvent (or, last, an error envelope). The stream ends after response.completed (or response.failed), with no [DONE] line.

Event: ResponseStreamEvent
typestringrequired

Allowed values: response.createdresponse.in_progressresponse.output_item.addedresponse.output_item.doneresponse.content_part.addedresponse.content_part.doneresponse.output_text.deltaresponse.output_text.doneresponse.function_call_arguments.deltaresponse.function_call_arguments.doneresponse.completedresponse.failedresponse.incomplete

sequence_numberinteger
responseResponse
10 child fields
idstringrequired
object"response"required
created_atintegerrequired

Unix time, in seconds.

statusstringrequired

Allowed values: completedfailedin_progressincomplete

modelstringrequired
outputResponseOutputItem[]required
5 child fields
typestringrequired
idstring
statusstring
rolestring
contentobject[]
usageResponseUsage
3 child fields
input_tokensintegerrequired
output_tokensintegerrequired
total_tokensintegerrequired
instructionsstring | null
max_output_tokensinteger | null
incomplete_detailsobject | null
output_indexinteger
content_indexinteger
item_idstring
itemResponseOutputItem

An output item: a message (with content parts of type output_text) or a function_call (call_id, name, arguments).

5 child fields
typestringrequired
idstring
statusstring
rolestring
contentobject[]
partobject
deltastring
textstring
argumentsstring
Event: Error
errorobjectrequired
3 child fields
codeintegerrequired

The HTTP status, as a number.

messagestringrequired

Safe to show to a user.

metadataobjectrequired
3 child fields
error_typestringrequired

The portable category. Branch on this for retry logic.

Allowed values: invalid_requestcontext_length_exceededauthenticationpayment_requiredpermission_deniedcontent_policy_violationnot_foundpayload_too_largerate_limit_exceededtimeoutserverprovider_unavailableprovider_overloaded

reasonstringrequired

The specific cause. Branch on this for user-facing handling.

Allowed values: invalid_bodykey_not_in_headercontext_length_exceededmissing_api_keyinvalid_api_keyinsufficient_creditskey_spend_cap_exceededaccount_suspendedcontent_policy_violationmodel_not_foundroute_not_foundupstream_timeoutrequest_in_progresspayload_too_largerate_limitedconcurrency_limitededge_rate_limitedinternal_errorprovider_errorservice_unavailableprovider_overloaded

modelstring

On model_not_found, the model id that was sent.

event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_0192f0e0","object":"response","created_at":1767225600,"status":"in_progress","model":"openai/gpt-4.1-mini","output":[]}}

event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"type":"message","id":"msg_0192f0e0","role":"assistant","status":"in_progress","content":[]}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":2,"item_id":"msg_0192f0e0","output_index":0,"content_index":0,"delta":"Habari!"}

event: response.output_text.done
data: {"type":"response.output_text.done","sequence_number":3,"item_id":"msg_0192f0e0","output_index":0,"content_index":0,"text":"Habari!"}

event: response.completed
data: {"type":"response.completed","sequence_number":4,"response":{"id":"resp_0192f0e0","object":"response","created_at":1767225600,"status":"completed","model":"openai/gpt-4.1-mini","output":[{"type":"message","id":"msg_0192f0e0","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Habari!"}]}],"usage":{"input_tokens":12,"output_tokens":3,"total_tokens":15}}}

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"
    }
  }
}