Réessayer en toute sécurité avec Idempotency-Key
Vous envoyez une requête qui coûte de l’argent, et la connexion se coupe. Hyperouter l’a-t-il reçue ? Vous ne pouvez pas le savoir. Si vous la renvoyez et qu’elle était arrivée, vous démarrez une seconde tâche et vous payez deux fois.
Un en-tête Idempotency-Key supprime le doute. Hyperouter reconnaît la répétition, ne démarre pas de seconde tâche, ne bloque pas de second montant, et répond à partir de la première requête.
Envoyez-en une sur chaque requête qui coûte de l’argent
Générez une nouvelle valeur aléatoire, par exemple un UUID v4, pour chaque nouvelle requête. Conservez-la avec la requête et envoyez la même valeur chaque fois que vous renvoyez cette requête.
curl https://api.hyperouter.app/v1/images/generations \
-H "Authorization: Bearer $HYPEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-d '{"model": "x-ai/grok-imagine-image", "prompt": "A lighthouse at dusk", "n": 1}'
Les OpenAI SDK n’envoient pas cet en-tête d’eux-mêmes. Passez-le comme en-tête supplémentaire ; les SDK le renvoient alors inchangé lors de leurs nouvelles tentatives automatiques.
import uuid key = str(uuid.uuid4()) # one per request; reuse it when you retry this request img = client.images.generate( model="x-ai/grok-imagine-image", prompt="A lighthouse at dusk", extra_headers={"Idempotency-Key": key}, )
import { randomUUID } from "node:crypto"; const key = randomUUID(); // one per request; reuse it when you retry this request const img = await client.images.generate( { model: "x-ai/grok-imagine-image", prompt: "A lighthouse at dusk" }, { headers: { "Idempotency-Key": key } }, );
Ce que rejoue chaque endpoint
| Endpoint | Une répétition renvoie |
|---|---|
POST /v1/images/generations, POST /v1/images/edits | L’image de la première requête, ou son 202 avec le même id sous Prefer: respond-async. |
POST /v1/videos, /v1/videos/edits, /v1/videos/extensions, /v1/videos/{id}/remix | L’objet Video de la première requête, dans son état actuel. |
POST /v1/chat/completions | 409 idempotency_key_replayed avec l’id de la première requête. |
Le chat est différent
Les réponses de chat ne sont pas rejouées. Une répétition avec la même clé répond 409 avec le code idempotency_key_replayed, et error.generation_id est l’id de la première requête. La requête n’est ni réexécutée ni refacturée.
{
"error": {
"message": "A request with this Idempotency-Key already ran as generation 019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17. ...",
"type": "conflict",
"code": "idempotency_key_replayed",
"param": null,
"generation_id": "019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17"
}
}
Retrouvez la première requête avec GET /v1/generations/{id} pour voir son statut, sa consommation et son coût. Si la première chat completion a échoué et n’a pas été facturée, sa clé est libérée et une répétition s’exécute comme une nouvelle requête.
Ainsi, pour le chat, la clé protège contre un double débit, elle ne sert pas à récupérer de nouveau la réponse. Lisez le 409 comme « elle a déjà été exécutée », et lisez l’enregistrement.
import os
import httpx
import openai
try:
res = client.chat.completions.create(
model="x-ai/grok-4.7",
messages=[{"role": "user", "content": "Hello"}],
extra_headers={"Idempotency-Key": key},
)
except openai.ConflictError as e:
body = e.body or {} # the error object
if body.get("code") != "idempotency_key_replayed":
raise
record = httpx.get(
f"https://api.hyperouter.app/v1/generations/{body['generation_id']}",
headers={"Authorization": f"Bearer {os.environ['HYPEROUTER_API_KEY']}"},
).json()["data"]
Fiez-vous à code, jamais à message.
Ce que reçoit une répétition
| Vous envoyez | Hyperouter répond |
|---|---|
| Une nouvelle clé | Exécute la requête normalement. |
| La même clé et la même requête, après acceptation de la première | Le résultat de la première requête, comme dans le tableau ci-dessus. Pas de seconde tâche, pas de second blocage, pas de second débit. |
| La même clé et la même requête, alors que la première est encore en cours d’acceptation | Attend la première, puis répond comme ci-dessus. Si la première n’a pas démarré sa tâche au bout de 60 secondes : 409 idempotency_key_in_use. Réessayez avec la même clé. |
| La même clé et une requête différente | 409 idempotency_key_reused. Rien ne s’exécute. Utilisez une nouvelle clé. |
La même clé, après que la première a été refusée avant le démarrage de toute tâche (un 400, un 402, ou un 5xx envoyé sans x-should-retry: false) | Exécute la requête comme une nouvelle, ce qui vous permet de la corriger et de la renvoyer avec la même clé. |
| La même clé, plus de 30 minutes après la première requête | Exécute la requête comme une nouvelle. |
| Une clé de plus de 255 caractères, ou qui n’est pas en ASCII imprimable | 400 invalid_idempotency_key. Rien ne s’exécute. |
Règles
- Portée. Une clé appartient à la clé API qui l’a envoyée. Deux clés API peuvent utiliser la même valeur sans s’influencer.
- Fenêtre. Une clé est honorée pendant 30 minutes après la première requête. Réessayez dans ce délai.
- Même requête. Deux requêtes sont identiques lorsque la méthode, le chemin et le corps correspondent. Le JSON est comparé après analyse : l’ordre des champs et les espaces n’ont pas d’importance. Pour un envoi multipart, les octets du fichier sont aussi comparés.
- Facturation. Une requête effectuée sous une clé est facturée au plus une fois, quel que soit le nombre de répétitions. Une répétition servie à partir de la première requête ne coûte rien.
- Garantie. Une clé ne démarre jamais plus d’une tâche, y compris lorsque deux répétitions arrivent en même temps.
Nouvelles tentatives automatiques dans les SDK
Les OpenAI SDK réessaient deux fois par défaut une réponse 408, 409, 429 ou 5xx, sauf si la réponse porte l’en-tête de réponse x-should-retry: false. Hyperouter envoie cet en-tête sur toute réponse pour laquelle une nouvelle tentative démarrerait une seconde tâche ou un second débit : le 503 timeout d’une image synchrone, un 500 après la création de la tâche, et 409 idempotency_key_reused et idempotency_key_replayed.
Il ne l’envoie jamais sur un 429, sur un 409 idempotency_key_in_use, ni sur un 5xx survenu avant l’existence de toute tâche. Dans ces cas, réessayer est sans risque. Avec une Idempotency-Key, chaque nouvelle tentative est sûre.
Les règles complètes se trouvent dans Nouvelles tentatives et idempotence.