Messages (Anthropic format)
Call router.africa with the Anthropic SDK or the Messages format. Change one line, keep the rest of your code.
POST /v1/messages (API reference) accepts requests in the Anthropic Messages format. If your code already uses the Anthropic SDK, you change the base URL and the key, and nothing else.
Change one line
Point the client at https://api.router.africa and give it your router.africa key as the usual api_key. The SDK sends it as x-api-key, which this endpoint accepts. The SDK adds /v1/messages itself, so don’t put /v1 in the base URL.
curl https://api.router.africa/v1/messages \
-H "x-api-key: $ROUTER_AFRICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4",
"max_tokens": 256,
"messages": [{ "role": "user", "content": "Write a two-line status message for a delayed parcel." }]
}'import os
import anthropic
client = anthropic.Anthropic(
base_url="https://api.router.africa", # the one line that changes
api_key=os.environ["ROUTER_AFRICA_API_KEY"], # sent as x-api-key
)
message = client.messages.create(
model="anthropic/claude-sonnet-4",
max_tokens=256,
messages=[{"role": "user", "content": "Write a two-line status message for a delayed parcel."}],
)
print(message.content[0].text)
print(message.usage)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.router.africa", // the one line that changes
apiKey: process.env.ROUTER_AFRICA_API_KEY, // sent as x-api-key
});
const message = await client.messages.create({
model: "anthropic/claude-sonnet-4",
max_tokens: 256,
messages: [{ role: "user", content: "Write a two-line status message for a delayed parcel." }],
});
console.log(message.content[0].text);
console.log(message.usage);Request
| Field | Notes |
|---|---|
model |
Required. A router.africa model id such as anthropic/claude-sonnet-4. The model must be switched on and allowed for your key. |
max_tokens |
Required. A value above the model’s limit is lowered to the limit. |
messages |
Required. The conversation, as the Anthropic Messages API defines it. |
system, temperature, stop_sequences, tools and the other Messages fields |
Passed through as the model’s API defines them. |
stream |
Set true for server-sent events. See below. |
thinking and cache_control are passed through as Anthropic defines them. Reasoning and cached tokens are counted in usage and charged at the published rate. fallbacks and models[] are not supported. See Compatibility notes.
You can send an Idempotency-Key header on this endpoint, as on chat completions.
Response
The response is the Anthropic message object: id, type, role, model, content, stop_reason, stop_sequence and usage. usage has input_tokens and output_tokens, and cache_creation_input_tokens and cache_read_input_tokens when caching is used. The header x-request-id identifies the request. You can look it up with GET /v1/generation.
Stream the response
Set stream: true. The events are the Anthropic ones (message_start, content_block_delta, message_delta, message_stop). The final message_delta event carries the output token count. If you disconnect, you are charged for the tokens used up to that point. See Streaming.
curl -N https://api.router.africa/v1/messages \
-H "x-api-key: $ROUTER_AFRICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4",
"max_tokens": 256,
"stream": true,
"messages": [{ "role": "user", "content": "Write a two-line status message for a delayed parcel." }]
}'import os
import anthropic
client = anthropic.Anthropic(
base_url="https://api.router.africa",
api_key=os.environ["ROUTER_AFRICA_API_KEY"],
)
with client.messages.stream(
model="anthropic/claude-sonnet-4",
max_tokens=256,
messages=[{"role": "user", "content": "Write a two-line status message for a delayed parcel."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print(stream.get_final_message().usage)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.router.africa",
apiKey: process.env.ROUTER_AFRICA_API_KEY,
});
const stream = client.messages.stream({
model: "anthropic/claude-sonnet-4",
max_tokens: 256,
messages: [{ role: "user", content: "Write a two-line status message for a delayed parcel." }],
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
console.log((await stream.finalMessage()).usage);Errors
Unlike every other endpoint, errors on POST /v1/messages use Anthropic’s format, so the Anthropic SDKs parse them as they do Anthropic’s own. error.type is Anthropic’s type, chosen from the HTTP status. error.reason is the specific cause, the same value as metadata.reason on other endpoints. There is no error.code and no metadata. Status codes and headers are the same as everywhere else. See Errors for the reasons.
{
"type": "error",
"error": {
"type": "billing_error",
"message": "This workspace doesn't have enough credits for this request. Add credits at https://app.router.africa.",
"reason": "insufficient_credits"
}
}
| Status | error.type |
|---|---|
400, 409, 413 |
invalid_request_error |
401 |
authentication_error |
402 |
billing_error |
403 |
permission_error |
404 |
not_found_error |
429 |
rate_limit_error |
408, 500, 502, 503 |
api_error |
529 |
overloaded_error |
An error after the stream has started can’t change the 200 status. It arrives as a final 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"}}