Idempotence
Retries sûrs pour les opérations d'écriture via le header Idempotency-Key (§1.3). Routes éligibles, TTL, stockage et sémantique de retry.
Les pannes réseau arrivent. Envoyer deux fois la même requête mutante peut créer des ressources en double (deux runs démarrés, deux clés générées, des cost rates facturés deux fois). La couche d’idempotence de CodeCourier résout ce problème : attachez un header Idempotency-Key et le serveur garantit un unique effet de bord, même si le client réessaie.
Comment ça marche
- Le client génère une clé unique par opération logique (UUIDv4 recommandé) et l’envoie dans le header
Idempotency-Key. - Le serveur hache la clé + méthode + path + body ; à la première requête, il exécute le handler et stocke la réponse complète.
- À chaque retry avec la même clé, la réponse stockée est rejouée octet par octet. Aucun effet de bord en double.
- Si un retry arrive avec la même clé mais un body différent, le serveur renvoie
409 idempotency_mismatch.
TTL
Les enregistrements d’idempotence sont conservés pendant 24 heures. Après cela, la clé est oubliée et la rejouer exécute une nouvelle requête. Concevez vos retries pour qu’ils se terminent largement dans cette fenêtre.
Routes éligibles
Tous les endpoints de mutation POST acceptent le header. Exemples notables :
POST /api/v1/runs/startPOST /api/v1/workflows/createPOST /api/v1/issues/createPOST /api/v1/project/api-keys/generatePOST /api/v1/sandboxes/createPOST /api/v1/cost-rates/upsertPOST /api/v1/recurring-tasks/create
GET, HEAD et DELETE sont idempotents par définition ; le header est ignoré (jamais une erreur) pour eux.
Endpoints PATCH
Seules les routes POST sont formellement documentées comme prenant en charge le header Idempotency-Key. Le seul endpoint PATCH actuellement exposé est traité par le serveur comme une opération d’écriture et acceptera le header sur la base du meilleur effort :
PATCH /api/v1/webhooks/endpoints/{id}- pris en charge
Pour toute future route PATCH, supposez que l’idempotence n’est pas garantie sauf documentation explicite sur cette route. Les clients intégrant de nouveaux endpoints PATCHdevraient implémenter une déduplication côté client (p. ex. un cache de requête en cours indexé par id de ressource + hash de body) jusqu’à confirmation d’un support de première classe.
Exemple
curl
IDEM=$(uuidgen)
curl -X POST https://<your-deployment>.convex.site/api/v1/runs/start \
-H "Authorization: Bearer cc_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEM" \
-d '{ "workflowId": "wf_abc", "input": { "topic": "hello" } }'TypeScript
import { randomUUID } from "node:crypto";
async function startRun(workflowId: string, input: unknown) {
const idempotencyKey = randomUUID();
const attempt = async () =>
fetch("https://<your-deployment>.convex.site/api/v1/runs/start", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CC_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ workflowId, input }),
});
// Retry up to 3x with exponential backoff - safe because of Idempotency-Key
for (let i = 0; i < 3; i++) {
const res = await attempt();
if (res.ok) return res.json();
if (res.status < 500) throw new Error(await res.text());
await new Promise((r) => setTimeout(r, 2 ** i * 500));
}
throw new Error("exhausted retries");
}Python
import os, uuid, time, requests
def start_run(workflow_id: str, input_payload: dict):
idem = str(uuid.uuid4())
headers = {
"Authorization": f"Bearer {os.environ['CC_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": idem,
}
body = {"workflowId": workflow_id, "input": input_payload}
for i in range(3):
r = requests.post(
"https://<your-deployment>.convex.site/api/v1/runs/start",
headers=headers, json=body, timeout=30,
)
if r.ok:
return r.json()
if r.status_code < 500:
r.raise_for_status()
time.sleep(2 ** i * 0.5)
raise RuntimeError("exhausted retries")Sémantique de retry
- Même clé + même body → rejoue la réponse stockée (200 OK si l’originale a réussi, l’erreur originale sinon).
- Même clé + body différent →
409 idempotency_mismatch. Générez une nouvelle clé pour la nouvelle opération. - Doublon en vol (deux retries en course) → la requête plus tardive se bloque brièvement, puis rejoue la réponse de la première.
Bonnes pratiques
- Générez la clé avant la première tentative ; réutilisez-la pour tous les retries de cette opération logique.
- Utilisez un UUIDv4 ou un ULID. Ne dérivez jamais la clé d’un état de requête mutable.
- Ne réutilisez pas les clés entre des opérations logiquement différentes.
- Loggez la clé aux côtés de la réponse pour la corrélation de traces.