Idempotency
How to repeat POST requests safely with Idempotency-Key and avoid duplicates.
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.
POST /api/v1/library/imports
Authorization: Bearer dk_live_...
Content-Type: application/json
Idempotency-Key: 8f3a5c2e-1b7d-4e90-a6c4-2d9b0e7f5a13Rules
| 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. The method, the path and the canonical JSON body are compared |
| A parallel request | 409 idempotency_in_progress with Retry-After (the remaining seconds) |
| An invalid key | 400 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
- Before the first attempt, generate a new UUID and keep it together with the operation.
- Send the same key on every attempt at that operation.
- 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.
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")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")