# Idempotency and retries

> How router.africa protects you from double charges when a request is sent twice, and how to retry safely.

Source: https://router.africa/docs/idempotency-and-retries

If a connection drops, you can't tell whether a request ran. Retrying could run it, and charge for it, twice. router.africa protects you in two ways.

## Automatic protection

You don't need to do anything. If the same account sends the exact same request body twice within about 30 seconds, the second request is treated as a duplicate:

- If the first request is still running, the second returns `409` with the reason `request_in_progress`. Wait, then retry.
- If the first request has finished, the second returns the stored response of the first. It is not run again and you are not charged again. The response has the header `x-idempotent-replay: true`. It means the response is a stored replay and not a new run. The header is absent on a new run.

After the window, an identical request is a new request. This is so that sending the same prompt twice on purpose still works.

## The `Idempotency-Key` header

router.africa adds an optional `Idempotency-Key` header. It isn't part of the OpenAI API. Send a unique value for each logical request, such as a UUID, and reuse the same value when you retry it. Requests with the same key are treated as one request for 24 hours.

**curl**

```bash
curl https://api.router.africa/v1/chat/completions \
  -H "Authorization: Bearer $ROUTER_AFRICA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a5e-3b7d-4c0e-9a41-0d2f8e7b1a90" \
  -d '{"model": "openai/gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}'
```

**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"])

key = str(uuid.uuid4())  # create once per logical request, reuse on retries

client.chat.completions.create(
    model="openai/gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={"Idempotency-Key": key},
)
```

**TypeScript**

```typescript
import OpenAI from "openai";

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

const key = randomUUID(); // create once per logical request, reuse on retries

await client.chat.completions.create(
  { model: "openai/gpt-4o-mini", messages: [{ role: "user", content: "Hello" }] },
  { headers: { "Idempotency-Key": key } },
);
```

{/* TODO: what happens when the same Idempotency-Key is reused with a different body, and whether streaming responses are replayed, are not decided in the sources. */}

## Which errors to retry

| Status | Retry? |
|---|---|
| `408`, `500`, `502`, `503`, `529` | Yes, with backoff. |
| `429` | Yes. Wait for `Retry-After`. |
| `409` | Yes, after a short wait. |
| `400`, `401`, `403`, `404`, `413` | No. Fix the request first. |
| `402` | No. Add credits or raise the key's cap first. |

The OpenAI SDKs retry connection errors and `408`, `409`, `429` and `5xx` by themselves. Set the number of retries with `max_retries` (Python) or `maxRetries` (TypeScript). A request that fails with an error is not charged, and its hold is released.

See [Errors](https://router.africa/docs/errors) and [Rate limits](https://router.africa/docs/rate-limits).
