# Idempotency

> How to repeat POST requests safely with Idempotency-Key and avoid duplicates.

Source: https://diko.app/en/api-docs/idempotency

## Why you need it

If a `POST /library/imports` request is interrupted and you do not know whether the import was created, repeating it could create a second import. `Idempotency-Key` solves that: the API recognises a repeated request with the same key and returns the same result without starting a new operation.

The header is supported on **every POST request** and recommended for `POST /library/imports`.

```http
POST /api/v1/library/imports
Authorization: Bearer dk_live_...
Content-Type: application/json
Idempotency-Key: 8f3a5c2e-1b7d-4e90-a6c4-2d9b0e7f5a13
```

## Rules

| Rule | Meaning |
|---|---|
| Format | 8 to 255 characters from `A-Za-z0-9_-:.`. Quotes are accepted too (`"abc..."`, the IETF Structured Field form) |
| Scope | the user: a key is valid across all their API keys |
| Retention | 24 hours |
| A repeat | the same status and body are returned with the header `Idempotent-Replayed: true` |
| The same key, a different request | 422 [`idempotency_key_reused`](https://diko.app/en/api-docs/errors.md#idempotency_key_reused). The method, the path and the canonical JSON body are compared |
| A parallel request | 409 [`idempotency_in_progress`](https://diko.app/en/api-docs/errors.md#idempotency_in_progress) with `Retry-After` (the remaining seconds) |
| An invalid key | 400 [`idempotency_key_invalid`](https://diko.app/en/api-docs/errors.md#idempotency_key_invalid) |

A running request holds a lease for 60 seconds. If its processing is interrupted, the next attempt with the same key can take it over once the lease has expired.

### What is stored and what is not

Only **2xx** responses and deterministic errors are stored: 400, 404, 413, 415 and 422. Responses **402, 403, 408, 409, 429 and 5xx are not stored** and the key is released. So after such an error you can safely repeat the same request with the same key once the problem is solved (for example, you topped up credits after a 402).

Responses that would contain secrets are never stored. The v1 API returns no such responses.

## How to use it

1. Before the first attempt, generate a **new UUID** and keep it together with the operation.
2. Send the same key on every attempt at **that** operation.
3. Generate a new key for a new operation. Do not use a constant such as `"import"`: a second request with a different body would get 422, and one with the same body would get the old response for 24 hours.

**Python**

```python
import os
import uuid

import requests

key = str(uuid.uuid4())  # created once and stored with the operation

last_error = None
for attempt in range(3):
    try:
        response = requests.post(
            "https://api.diko.app/api/v1/library/imports",
            headers={
                "Authorization": f"Bearer {os.environ['DIKO_API_KEY']}",
                "Idempotency-Key": key,
            },
            json={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"},
            timeout=30,
        )
        break
    except (requests.ConnectionError, requests.Timeout) as error:
        last_error = error  # same key: a retry cannot create a duplicate
else:
    raise last_error  # all 3 attempts failed

if response.headers.get("Idempotent-Replayed") == "true":
    print("This is the result of an earlier request")
```

**TypeScript**

```typescript
const key = crypto.randomUUID() // created once and stored with the operation

let response: Response | undefined
for (let attempt = 0; attempt < 3 && !response; attempt++) {
  try {
    response = await fetch("https://api.diko.app/api/v1/library/imports", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.DIKO_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": key, // same key: no duplicate
      },
      body: JSON.stringify({ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }),
      signal: AbortSignal.timeout(30_000),
    })
  } catch {
    // network error or timeout: retry with the same key
  }
}
if (!response) throw new Error("No response after 3 attempts")
if (response.headers.get("Idempotent-Replayed") === "true") console.log("This is the result of an earlier request")
```

> **Note:**
> Imports have a further guard against duplicates that does not depend on the key: if an import of the same video is already running, `POST /library/imports` returns **200** with the existing import (a difference in `language` is ignored).
