# Messages (Anthropic format)

> Call router.africa with the Anthropic SDK or the Messages format. Change one line, keep the rest of your code.

Source: https://router.africa/docs/messages

`POST /v1/messages` ([API reference](https://router.africa/docs/api-reference/create-message)) 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**

```bash
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." }]
  }'
```

**Python**

```python
import os

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)
```

**TypeScript**

```typescript
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);
```

> **Note:** This endpoint accepts the key as `x-api-key` or as `Authorization: Bearer`. Every other endpoint accepts `Authorization: Bearer` only. See [Authentication](https://router.africa/docs/authentication).

## 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](https://router.africa/docs/compatibility-notes).

You can send an [`Idempotency-Key`](https://router.africa/docs/idempotency-and-retries) 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`](https://router.africa/docs/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](https://router.africa/docs/streaming).

**curl**

```bash
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." }]
  }'
```

**Python**

```python
import os

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)
```

**TypeScript**

```typescript
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](https://router.africa/docs/errors) for the reasons.

```json
{
  "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.

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