# Create a response (OpenAI Responses format)

Source: https://router.africa/docs/api-reference/create-response

`POST https://api.router.africa/v1/responses`

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.

## Authentication

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

## Parameters

- `Authorization` (header, string, required): Bearer YOUR_KEY.
- `Idempotency-Key` (header, string): router.africa extension. Works exactly as on `POST /v1/chat/completions`.

## Request body

- `model` (string, required): A model id from `GET /v1/models`.
- `input` (string | ResponseInputItem[], required): Text, or an array of input items.
  - `variant 1` (ResponseInputItem[])
    - `type` (string)
    - `role` ("system" | "developer" | "user" | "assistant")
    - `content` (string | any[]): Text, or an array of content parts.
- `instructions` (string): A system instruction for the model.
- `stream` (boolean): Default: `false`
- `temperature` (number)
- `max_output_tokens` (integer): 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
- `tools` (object[]): Function tools.
  - `type` ("function", required)
  - `name` (string, required)
  - `description` (string)
  - `parameters` (object)
  - `strict` (boolean | null)

## Example request

**curl**

```bash
curl https://api.router.africa/v1/responses \
  -H "Authorization: Bearer $ROUTER_AFRICA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4.1-mini",
    "input": "Habari! Say hello in Swahili.",
    "max_output_tokens": 100
  }'
```

**Python**

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.router.africa/v1",
    api_key=os.environ["ROUTER_AFRICA_API_KEY"],
)

response = client.responses.create(
    model="openai/gpt-4.1-mini",
    input="Habari! Say hello in Swahili.",
    max_output_tokens=100,
)
print(response.output_text)
```

**TypeScript**

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.router.africa/v1",
  apiKey: process.env.ROUTER_AFRICA_API_KEY,
});

const response = await client.responses.create({
  model: "openai/gpt-4.1-mini",
  input: "Habari! Say hello in Swahili.",
  max_output_tokens: 100,
});
console.log(response.output_text);
```

## Response

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

- `id` (string, required)
- `object` ("response", required)
- `created_at` (integer, required): Unix time, in seconds.
- `status` ("completed" | "failed" | "in_progress" | "incomplete", required)
- `model` (string, required)
- `output` (ResponseOutputItem[], required)
  - `type` (string, required)
  - `id` (string)
  - `status` (string)
  - `role` (string)
  - `content` (object[])
- `usage` (ResponseUsage)
  - `input_tokens` (integer, required)
  - `output_tokens` (integer, required)
  - `total_tokens` (integer, required)
- `instructions` (string | null)
- `max_output_tokens` (integer | null)
- `incomplete_details` (object | null)

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

## 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 schema `ResponseStreamEvent`:

- `type` ("response.created" | "response.in_progress" | "response.output_item.added" | "response.output_item.done" | "response.content_part.added" | "response.content_part.done" | "response.output_text.delta" | "response.output_text.done" | "response.function_call_arguments.delta" | "response.function_call_arguments.done" | "response.completed" | "response.failed" | "response.incomplete", required)
- `sequence_number` (integer)
- `response` (Response)
  - `id` (string, required)
  - `object` ("response", required)
  - `created_at` (integer, required): Unix time, in seconds.
  - `status` ("completed" | "failed" | "in_progress" | "incomplete", required)
  - `model` (string, required)
  - `output` (ResponseOutputItem[], required)
    - `type` (string, required)
    - `id` (string)
    - `status` (string)
    - `role` (string)
    - `content` (object[])
  - `usage` (ResponseUsage)
    - `input_tokens` (integer, required)
    - `output_tokens` (integer, required)
    - `total_tokens` (integer, required)
  - `instructions` (string | null)
  - `max_output_tokens` (integer | null)
  - `incomplete_details` (object | null)
- `output_index` (integer)
- `content_index` (integer)
- `item_id` (string)
- `item` (ResponseOutputItem): An output item: a `message` (with `content` parts of type `output_text`) or a `function_call` (`call_id`, `name`, `arguments`). 
  - `type` (string, required)
  - `id` (string)
  - `status` (string)
  - `role` (string)
  - `content` (object[])
- `part` (object)
- `delta` (string)
- `text` (string)
- `arguments` (string)

Event schema `Error`:

- `error` (object, required)
  - `code` (integer, required): The HTTP status, as a number.
  - `message` (string, required): Safe to show to a user.
  - `metadata` (object, required)
    - `error_type` ("invalid_request" | "context_length_exceeded" | "authentication" | "payment_required" | "permission_denied" | "content_policy_violation" | "not_found" | "payload_too_large" | "rate_limit_exceeded" | "timeout" | "server" | "provider_unavailable" | "provider_overloaded", required): The portable category. Branch on this for retry logic.
    - `reason` ("invalid_body" | "key_not_in_header" | "context_length_exceeded" | "missing_api_key" | "invalid_api_key" | "insufficient_credits" | "key_spend_cap_exceeded" | "account_suspended" | "content_policy_violation" | "model_not_found" | "route_not_found" | "upstream_timeout" | "request_in_progress" | "payload_too_large" | "rate_limited" | "concurrency_limited" | "edge_rate_limited" | "internal_error" | "provider_error" | "service_unavailable" | "provider_overloaded", required): The specific cause. Branch on this for user-facing handling.
    - `model` (string): On `model_not_found`, the model id that was sent.

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

### 400

The request is malformed or fails validation.

400 invalid_body

```json
{
  "error": {
    "code": 400,
    "message": "Request body is invalid: `messages` is required.",
    "metadata": {
      "error_type": "invalid_request",
      "reason": "invalid_body"
    }
  }
}
```

400 key_not_in_header

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

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

### 401

The API key is missing or not valid. One answer for unknown, malformed, revoked, expired and pending keys.

401 missing_api_key

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

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

Not enough credits, or the key's spend cap is reached. Do not retry until the cause is fixed.

402 insufficient_credits

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

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

The account is suspended, or the provider refused the content.

403 account_suspended

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

403 content_policy_violation

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

The model is unknown, off or not allowed for this key (one answer for all three), or the path does not exist.

404 model_not_found

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

404 route_not_found

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

### 408

The model provider took too long, after retries and fallbacks.

408 upstream_timeout

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

### 409

An identical request (same body, or same `Idempotency-Key`) is still running. Wait and retry; the first request's result is returned.

409 request_in_progress

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

### 413

The request body is over 16 MiB.

413 payload_too_large

```json
{
  "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 limit reached. Back off and honour `Retry-After`. No `x-ratelimit-*` headers are sent.

429 rate_limited

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

429 concurrency_limited

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

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

### 500

A fault on our side. Retry once, then contact support with the request id.

500 internal_error

```json
{
  "error": {
    "code": 500,
    "message": "Something went wrong on our side. Reference: 0192f0e0-7b1a-7c3e-9d4f-5a6b7c8d9e0f.",
    "metadata": {
      "error_type": "server",
      "reason": "internal_error"
    }
  }
}
```

### 502

The model provider returned an error after fallbacks.

502 provider_error

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

### 503

Temporarily unavailable; refused before any model was called.

503 service_unavailable

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

### 529

The model provider is overloaded and fallbacks are exhausted.

529 provider_overloaded

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