---
title: Idempotency
description: The Idempotency-Key header makes retrying a generation submit safe — the same request returns the same request_id with no second job and no second charge.
icon: Repeat
---

Send an `Idempotency-Key` header when you submit a generation. If the connection drops and you retry with the same key and the same body, Clipia returns **the same response** with the same `request_id`: no second job is created and no credits are charged twice. The record is kept for 24 hours.

## Header

<TypeTable
  type={{
    'Idempotency-Key': {
      type: 'string',
      description: 'Key for one logical request, up to 255 characters. A UUID v4 is recommended. Valid for 24 hours within one API key.',
    },
  }}
/>

## Usage

<Tabs items={['cURL', 'TypeScript', 'Python']}>
<Tab value="cURL">

```bash
curl -X POST https://api.clipia.ai/v1/models/nano-banana-2 \
  -H "Authorization: Bearer $CLIPIA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f3a1c7e-2b41-4d2a-9c0e-1200304cf45b" \
  -d '{ "input": { "prompt": "a sunset over mountains, cinematic" } }'
```

</Tab>
<Tab value="TypeScript">

```ts
import { createClient } from 'clipia-ai';

const clipia = createClient({ apiKey: process.env.CLIPIA_KEY! });

// The SDK adds an Idempotency-Key to every submit on its own.
// To make your own retries land on the same job, pass your key:
await clipia.queue.submit('nano-banana-2', {
  input: { prompt: 'a sunset over mountains, cinematic' },
  idempotencyKey: '8f3a1c7e-2b41-4d2a-9c0e-1200304cf45b',
});
```

</Tab>
<Tab value="Python">

```python
import os
from clipia import Clipia

client = Clipia(api_key=os.environ["CLIPIA_KEY"])

# The SDK adds an Idempotency-Key to every submit; pass your own if you need to.
client.submit(
    "nano-banana-2",
    {"prompt": "a sunset over mountains, cinematic"},
    idempotency_key="8f3a1c7e-2b41-4d2a-9c0e-1200304cf45b",
)
```

</Tab>
</Tabs>

Create a new key for each **logical** request, not for each network attempt. When the SDK generates the key, each `submit` call gets its own; a retry in your code that makes a new call gets a new key.

## Retry behavior

| Situation | Result |
|-----------|--------|
| Same key, same model, same body | The same response in full (including `warnings`, if there were any) and the same `request_id`. No new job, no new charge |
| Same key, different model or body | `409 idempotency_key_reuse` |
| Retry while the first request is still being processed | `409 request_in_progress` — retry in a few seconds |
| The first request ended with an error | The key is released and the retry is processed from scratch |
| More than 24 hours have passed | The key counts as new and creates a new job |

Keys don't overlap across API keys: the same `Idempotency-Key` on two API keys means two separate requests.

Every replayed response — with an explicit key or merged without the header — carries the `Idempotent-Replayed: true` header; the response to a new job does not.

## Without the header

Without the header, identical requests from one API key that arrive within 20 seconds are also merged into one job. This guards against an accidental double click or a quick network retry. If you deliberately want two identical generations in a row, send different `Idempotency-Key` values.

<Callout type="warn" title="Where it applies">
The header applies to `POST /v1/models/{model}` and to non-streaming `POST /v1/chat/completions` and `POST /v1/completions`; with `stream: true` it is ignored. Merging without the header happens only on generation submit. `GET` requests are safe to retry without it. Don't put an email or other personal data in the key — use a random UUID v4.
</Callout>
