> ## Documentation Index
> Fetch the complete documentation index at: https://docs.routerbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Retry media generations safely and recover tasks after a dropped connection.

## Why

A media generation is charged when it succeeds. If your connection drops
*after* RouterBase accepts the request but *before* the response delivers the
generation `id`, you are left with a task you cannot poll — and a blind retry
would create (and charge) a second generation.

Supplying an idempotency id closes both gaps:

* **Retries are safe.** A request with an id you already used replays the
  existing generation — same `id`, current `status`, and `data` when it has
  finished — instead of creating a new one. No double charge.
* **Lost tasks are recoverable.** `GET /v1/videos/generations?client_request_id=...`
  returns the generation created under that id, even if you never saw the
  original response.

Supported on `POST /v1/images/generations`, `POST /v1/videos/generations`,
`POST /v1/audio/speech`, and `POST /v1/audio/generations`. Requests without
an id behave exactly as before.

## Supplying an id

Send either an `Idempotency-Key` header (the header wins if both are present)
or a `client_request_id` field in the JSON body. Use a fresh unique value per
logical request — a UUID is ideal. Ids are 1–128 visible ASCII characters and
are scoped to your account.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://routerbase.com/v1/videos/generations \
    -H "Authorization: Bearer sk-rb-xxxxxxxxxxxx" \
    -H "Idempotency-Key: 0b8f6a2e-4d1c-4f3a-9e57-1c2d3e4f5a6b" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/sora-2-t2v",
      "prompt": "A slow pan across a foggy harbor at dawn"
    }'
  ```

  ```python Python theme={null}
  import uuid, requests

  key = str(uuid.uuid4())          # persist this before sending
  resp = requests.post(
      "https://routerbase.com/v1/videos/generations",
      headers={
          "Authorization": "Bearer sk-rb-xxxxxxxxxxxx",
          "Idempotency-Key": key,
      },
      json={
          "model": "openai/sora-2-t2v",
          "prompt": "A slow pan across a foggy harbor at dawn",
      },
      timeout=300,
  )
  ```

  ```json Body field alternative theme={null}
  {
    "model": "openai/sora-2-t2v",
    "prompt": "A slow pan across a foggy harbor at dawn",
    "client_request_id": "0b8f6a2e-4d1c-4f3a-9e57-1c2d3e4f5a6b"
  }
  ```
</CodeGroup>

Persist the id **before** sending the request — it is your handle to the task
if the response never arrives.

## Recovering after a dropped connection

If the POST times out or the connection resets, do **not** immediately assume
the task was lost — look it up:

```bash theme={null}
curl "https://routerbase.com/v1/videos/generations?client_request_id=0b8f6a2e-4d1c-4f3a-9e57-1c2d3e4f5a6b" \
  -H "Authorization: Bearer sk-rb-xxxxxxxxxxxx"
```

* **`200`** — the request did go through. The response is the standard
  generation object (`id`, `status`, `data[].url` when finished); continue
  polling `GET /v1/videos/generations/{id}` as usual.
* **`404`** — the request never reached RouterBase. Retry the POST with the
  **same** id; if the 404 raced a request still being accepted, the retry
  simply replays it.

The same lookup exists on `/v1/images/generations` and
`/v1/audio/generations`.

## Retry semantics

* A replayed request returns the stored generation in whatever state it is
  in (`pending`, `success`, or `failed`) — it does not re-run the task, and
  the request body of the retry is ignored.
* A `failed` generation is **not** retried under the same id (you were not
  charged for it). Use a fresh id to try again.
* Two concurrent requests with the same id are safe: exactly one generation
  is created, and the other request replays it.
