Idempotence
Une écriture peut échouer à moitié : ta connexion tombe. Ton logiciel ne sait alors pas si Rally a reçu la requête. Une Idempotency-Key te laisse la renvoyer sans risque de doublon.
Sur quelles requêtes
Envoie une Idempotency-Key sur chaque POST. Elle est facultative, mais sans elle un nouvel envoi crée un doublon.
- Prends une valeur unique par requête, par exemple un UUID.
- Elle n’est jamais vide et fait 255 caractères au plus. Sinon l’API répond
INVALID_INPUT. - Rally la garde 24 heures.
curl https://gorally.games/v1/leagues \
-X POST \
-H "Authorization: Bearer $RALLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6a1f0c3e-creer-ligue-mardi" \
-d '{"name":"Ligue du mardi","sport":"CURLING","timeZone":"America/Toronto"}'const response = await fetch('https://gorally.games/v1/leagues', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RALLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6a1f0c3e-creer-ligue-mardi',
},
body: JSON.stringify({
"name": "Ligue du mardi",
"sport": "CURLING",
"timeZone": "America/Toronto"
}),
});
const data = await response.json();import os
import requests
response = requests.post(
"https://gorally.games/v1/leagues",
headers={
"Authorization": f"Bearer {os.environ['RALLY_API_KEY']}",
"Idempotency-Key": "6a1f0c3e-creer-ligue-mardi",
},
json={
"name": "Ligue du mardi",
"sport": "CURLING",
"timeZone": "America/Toronto",
},
timeout=10,
)
data = response.json()Ce que fait l’API
Rally compare la clé et le corps de la requête, octet pour octet.
- Première fois : la requête s’exécute, et sa réponse est mémorisée.
- Même clé, même corps : Rally rejoue la réponse d’origine, sans rien réécrire. Le rejeu porte l’en-tête
Idempotent-Replayed: true. - Même clé, autre corps : l’API refuse.
Un refus rendu pendant l’exécution est rejoué comme un succès : droits manquants, élément introuvable, conflit. Une 5xx n’est jamais mémorisée : elle veut dire « réessaie ».
Un corps refusé à la lecture ne réserve pas la clé : JSON mal formé, champ invalide ou manquant. Corrige la requête et renvoie-la avec la même clé : elle s’exécute, sans Idempotent-Replayed.
Le rejeu porte le request_id de ta nouvelle requête, pas celui de l’originale.
Les trois erreurs
Elles répondent toutes 409.
| Code | Cause | Que faire |
|---|---|---|
IDEMPOTENCY_KEY_REUSED |
Cette clé a déjà servi avec un corps différent. | Prends une clé neuve pour cette nouvelle requête. |
IDEMPOTENT_REQUEST_IN_FLIGHT |
Une requête avec cette clé est encore en cours. | Réessaie dans un instant, avec la même clé. |
IDEMPOTENCY_OUTCOME_UNKNOWN |
Une requête avec cette clé s’est interrompue sans issue connue. | Relis l’élément pour savoir s’il existe, puis reprends avec une clé neuve. |
Pour IDEMPOTENCY_OUTCOME_UNKNOWN, Rally ne rejoue pas : une connexion perdue juste après l’enregistrement ressemble à une perdue juste avant. Rejouer risquerait le doublon que la clé sert à éviter.
Pas sur les PATCH
N’envoie jamais d’Idempotency-Key sur un PATCH. L’API l’ignore.
PATCH /matches/{matchId} se protège autrement, par un verrou de révision. Tu envoies expectedRevision, la révision que tu as lue. Si quelqu’un a écrit entre-temps, l’API répond CONFLICT.
curl https://gorally.games/v1/matches/f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c \
-X PATCH \
-H "Authorization: Bearer $RALLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expectedRevision":3,"event":"COMPLETER","score":{"a":7,"b":5,"unitsPlayed":8}}'const response = await fetch('https://gorally.games/v1/matches/f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.RALLY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"expectedRevision": 3,
"event": "COMPLETER",
"score": {
"a": 7,
"b": 5,
"unitsPlayed": 8
}
}),
});
const data = await response.json();import os
import requests
response = requests.patch(
"https://gorally.games/v1/matches/f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
headers={"Authorization": f"Bearer {os.environ['RALLY_API_KEY']}"},
json={
"expectedRevision": 3,
"event": "COMPLETER",
"score": {
"a": 7,
"b": 5,
"unitsPlayed": 8,
},
},
timeout=10,
)
data = response.json()Après un CONFLICT, relis la rencontre et décide. Ne renvoie pas à l’aveugle : tu écraserais le travail de l’autre programme.
Et ensuite
- Lis le guide « Pagination » pour parcourir les listes.
- Garde le guide « Erreurs » sous la main pour les autres codes.