{"openapi":"3.1.0","info":{"title":"Rally — API des ligues","version":"1.0.0","description":"Façade programme des ligues Rally. L’authentification se fait par CLÉ D’ORGANISATION (`Authorization: Bearer rk_live_…`) et par elle seule : un cookie de session n’ouvre rien ici, et l’organisation n’est jamais nommée dans une requête — elle est déduite de la clé. Une clé n’est pas une personne : elle n’a ni gouvernance, ni délégation, ni accès personnel implicite. L’administration des clés et des webhooks appartient à la session, jamais à une clé. Chaque réponse porte l’en-tête `X-Request-Id`. Ce document existe en anglais (`/v1/openapi.json`) et en français (`/v1/openapi.fr.json`) ; les messages d’erreur rendus par l’API sont en français.","contact":{"name":"Rally","url":"https://gorally.games","email":"hello@gorally.games"}},"servers":[{"url":"https://gorally.games/v1"}],"security":[{"cleDOrganisation":[]}],"tags":[{"name":"Compétition","description":"Ligues, saisons, catégories, classements."},{"name":"Résultats","description":"Rencontres et saisie de résultat."},{"name":"Effectifs","description":"Sous scopes `members:*` uniquement — la seule surface qui porte des noms de personnes."},{"name":"Événements","description":"Le même flux que les webhooks, relisible et rejouable."}],"paths":{"/openapi.json":{"get":{"operationId":"lireContrat","tags":["Compétition"],"security":[],"summary":"Ce document, en anglais.","description":"Sans clé : le contrat se lit avant de s’intégrer.","responses":{"200":{"description":"Le document OpenAPI, en anglais.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object"},"example":{"openapi":"3.1.0","info":{"title":"Rally Leagues API","version":"1.0.0"}}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/openapi.fr.json":{"get":{"operationId":"lireContratFrancais","tags":["Compétition"],"security":[],"summary":"Ce document, en français.","description":"Sans clé, comme la version anglaise.","responses":{"200":{"description":"Le document OpenAPI, en français.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object"},"example":{"openapi":"3.1.0","info":{"title":"Rally — API des ligues","version":"1.0.0"}}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/leagues":{"get":{"operationId":"listerLigues","tags":["Compétition"],"summary":"Les ligues de l’organisation.","description":"Scope `read`. Pagination par curseur sur une clé immuable : une ligne existante est vue exactement une fois, même sous mutation.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":25,"description":"Taille de page. Au-delà de 100, la requête est refusée plutôt que rognée en silence."},{"name":"after","in":"query","required":false,"schema":{"type":"string"},"example":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","description":"Curseur : l’identifiant du DERNIER élément de la page précédente. Un curseur inconnu est refusé — il ne se rattrape pas. Les trois listes paginées — événements, ligues, rencontres — suivent la MÊME règle : le parcours ordonne sur un numéro d’insertion interne qu’aucune modification ne déplace, et une page s’arrête avant toute écriture dont la transaction peut encore être en vol. Une ligne écrite par une transaction longue est donc RETARDÉE, jamais sautée : elle apparaît à sa place dès que sa transaction est commise, et exactement une fois. Renommer une ligue ou corriger une rencontre entre deux pages ne la fait ni disparaître ni revenir."}],"responses":{"200":{"description":"Une page de ligues.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/League"}},"has_more":{"type":"boolean"}}},"example":{"object":"list","data":[{"id":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","name":"Ligue du mardi","sport":"CURLING","sportLabel":null,"profile":{"key":"CURLING","label":"Curling","version":3,"resourceKind":"SHEET","cadence":{"unitsPerMatch":8,"minutesPerUnit":15,"matchesPerNight":1,"scoreTarget":null,"seasonWeeks":{"min":10,"max":30}},"vocabulary":{"resource":{"one":"feuille","many":"feuilles","genre":"F"},"unit":{"one":"bout","many":"bouts","genre":"M"},"match":{"one":"partie","many":"parties","genre":"F"},"scoreUnit":{"one":"point","many":"points","genre":"M"}}},"timeZone":"America/Toronto"}],"has_more":false}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"creerLigue","tags":["Compétition"],"summary":"Crée une ligue.","description":"Scope `write`. Le droit commercial de l’organisation est consulté comme pour toute écriture.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"example":"6a1f0c3e-creer-ligue-mardi","description":"Rejoue la réponse d’origine pendant 24 heures : un succès, ou un refus rendu pendant l’exécution. Un corps refusé avant (JSON mal formé, champ invalide) ne réserve pas la clé. Même clé et corps différent : 409 `IDEMPOTENCY_KEY_REUSED`. Une exécution encore en vol répond 409 `IDEMPOTENT_REQUEST_IN_FLIGHT`. Une 5xx n’est jamais figée : elle dit « réessaie ». Et si une exécution s’interrompt sans que son issue soit connue — une connexion perdue juste après le commit rend la même erreur qu’une perdue juste avant — le rejeu répond 409 `IDEMPOTENCY_OUTCOME_UNKNOWN` plutôt que de risquer un doublon : relis la ressource, puis reprends avec une clé neuve."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLeague"},"example":{"name":"Ligue du mardi","sport":"CURLING","timeZone":"America/Toronto"}}}},"responses":{"201":{"description":"La ligue créée.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/League"},"example":{"id":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","name":"Ligue du mardi","sport":"CURLING","sportLabel":null,"profile":{"key":"CURLING","label":"Curling","version":3,"resourceKind":"SHEET","cadence":{"unitsPerMatch":8,"minutesPerUnit":15,"matchesPerNight":1,"scoreTarget":null,"seasonWeeks":{"min":10,"max":30}},"vocabulary":{"resource":{"one":"feuille","many":"feuilles","genre":"F"},"unit":{"one":"bout","many":"bouts","genre":"M"},"match":{"one":"partie","many":"parties","genre":"F"},"scoreUnit":{"one":"point","many":"points","genre":"M"}}},"timeZone":"America/Toronto"}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejeu divergent de la clé d’idempotence.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Corps au-delà de 16 Kio (`BODY_TOO_LARGE`). La façade publique ne transporte aucun fichier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/leagues/{leagueId}":{"get":{"operationId":"lireLigue","tags":["Compétition"],"summary":"Une ligue et ses saisons.","description":"Scope `read`.","parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string"},"example":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f"}],"responses":{"200":{"description":"La ligue et ses saisons.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueDetail"},"example":{"object":"league","id":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","name":"Ligue du mardi","sport":"CURLING","sportLabel":null,"profile":{"key":"CURLING","label":"Curling","version":3,"resourceKind":"SHEET","cadence":{"unitsPerMatch":8,"minutesPerUnit":15,"matchesPerNight":1,"scoreTarget":null,"seasonWeeks":{"min":10,"max":30}},"vocabulary":{"resource":{"one":"feuille","many":"feuilles","genre":"F"},"unit":{"one":"bout","many":"bouts","genre":"M"},"match":{"one":"partie","many":"parties","genre":"F"},"scoreUnit":{"one":"point","many":"points","genre":"M"}}},"timeZone":"America/Toronto","cadences":[{"id":"7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f","key":"HIVER","label":"Hiver","position":1}],"seasons":[{"id":"c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e","name":"Hiver 2027","cadenceId":"7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f","ordinal":1,"status":"SETUP","sportProfile":"CURLING","sportProfileVersion":3,"resourceKind":"SHEET","cadence":{"unitsPerMatch":8,"minutesPerUnit":15,"matchesPerNight":1,"scoreTarget":null,"seasonWeeks":{"min":10,"max":30}},"vocabulary":{"resource":{"one":"feuille","many":"feuilles","genre":"F"},"unit":{"one":"bout","many":"bouts","genre":"M"},"match":{"one":"partie","many":"parties","genre":"F"},"scoreUnit":{"one":"point","many":"points","genre":"M"}}}]}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/leagues/{leagueId}/seasons":{"post":{"operationId":"creerSaison","tags":["Compétition"],"summary":"Crée une saison dans une ligue.","description":"Scope `write`.","parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string"},"example":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"example":"6a1f0c3e-creer-ligue-mardi","description":"Rejoue la réponse d’origine pendant 24 heures : un succès, ou un refus rendu pendant l’exécution. Un corps refusé avant (JSON mal formé, champ invalide) ne réserve pas la clé. Même clé et corps différent : 409 `IDEMPOTENCY_KEY_REUSED`. Une exécution encore en vol répond 409 `IDEMPOTENT_REQUEST_IN_FLIGHT`. Une 5xx n’est jamais figée : elle dit « réessaie ». Et si une exécution s’interrompt sans que son issue soit connue — une connexion perdue juste après le commit rend la même erreur qu’une perdue juste avant — le rejeu répond 409 `IDEMPOTENCY_OUTCOME_UNKNOWN` plutôt que de risquer un doublon : relis la ressource, puis reprends avec une clé neuve."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSeason"},"example":{"name":"Hiver 2027","cadenceId":"7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f","startsAt":"2027-01-05T00:00:00.000Z","endsAt":"2027-04-27T00:00:00.000Z"}}}},"responses":{"201":{"description":"La saison créée.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Season"},"example":{"id":"c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e","name":"Hiver 2027","cadenceId":"7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f","ordinal":1,"status":"SETUP","sportProfile":"CURLING","sportProfileVersion":3,"resourceKind":"SHEET","cadence":{"unitsPerMatch":8,"minutesPerUnit":15,"matchesPerNight":1,"scoreTarget":null,"seasonWeeks":{"min":10,"max":30}},"vocabulary":{"resource":{"one":"feuille","many":"feuilles","genre":"F"},"unit":{"one":"bout","many":"bouts","genre":"M"},"match":{"one":"partie","many":"parties","genre":"F"},"scoreUnit":{"one":"point","many":"points","genre":"M"}}}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejeu divergent de la clé d’idempotence.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Corps au-delà de 16 Kio (`BODY_TOO_LARGE`). La façade publique ne transporte aucun fichier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/seasons/{seasonId}/categories":{"get":{"operationId":"listerCategories","tags":["Compétition"],"summary":"Les catégories d’une saison.","description":"Scope `read`.","parameters":[{"name":"seasonId","in":"path","required":true,"schema":{"type":"string"},"example":"c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e"}],"responses":{"200":{"description":"Les catégories de la saison, rendues entières.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}},"example":{"object":"list","data":[{"id":"e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f","seasonId":"c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e","name":"Mardi soir","phase":"REGULAR","revision":6,"policyRevision":1,"matchCount":28,"unplayedMatchCount":20,"unplayedRegularMatchCount":20,"registrationCapacity":8}]}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/categories/{categoryId}/standings":{"get":{"operationId":"lireClassement","tags":["Compétition"],"summary":"Le classement d’une catégorie.","description":"Scope `read`. Le classement est CALCULÉ à la lecture et n’est jamais stocké : c’est pourquoi il n’existe aucun webhook `standings.updated`. Un consommateur qui veut le suivre écoute `match.completed` et relit ceci.","parameters":[{"name":"categoryId","in":"path","required":true,"schema":{"type":"string"},"example":"e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f"}],"responses":{"200":{"description":"Le classement, ordonné et rendu entier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/StandingRow"}}}},"example":{"object":"list","data":[{"leagueTeamId":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","name":"Les Pierres","rank":1,"tied":false,"played":4,"wins":3,"draws":0,"losses":1,"scoreFor":29,"scoreAgainst":18,"diff":11,"gamePointsFor":0,"gamePointsAgainst":0,"points":9},{"leagueTeamId":"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e","name":"Les Balais","rank":2,"tied":false,"played":4,"wins":2,"draws":1,"losses":1,"scoreFor":24,"scoreAgainst":21,"diff":3,"gamePointsFor":0,"gamePointsAgainst":0,"points":7}]}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/matches":{"get":{"operationId":"listerRencontres","tags":["Résultats"],"summary":"Les rencontres de l’organisation.","description":"Scope `read`. `revision` est le verrou optimiste à annoncer pour écrire un résultat.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":25,"description":"Taille de page. Au-delà de 100, la requête est refusée plutôt que rognée en silence."},{"name":"after","in":"query","required":false,"schema":{"type":"string"},"example":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","description":"Curseur : l’identifiant du DERNIER élément de la page précédente. Un curseur inconnu est refusé — il ne se rattrape pas. Les trois listes paginées — événements, ligues, rencontres — suivent la MÊME règle : le parcours ordonne sur un numéro d’insertion interne qu’aucune modification ne déplace, et une page s’arrête avant toute écriture dont la transaction peut encore être en vol. Une ligne écrite par une transaction longue est donc RETARDÉE, jamais sautée : elle apparaît à sa place dès que sa transaction est commise, et exactement une fois. Renommer une ligue ou corriger une rencontre entre deux pages ne la fait ni disparaître ni revenir."}],"responses":{"200":{"description":"Une page de rencontres.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Match"}},"has_more":{"type":"boolean"}}},"example":{"object":"list","data":[{"id":"f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c","categoryId":"e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f","status":"SCHEDULED","teamAId":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","teamBId":"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e","scoreA":null,"scoreB":null,"sets":null,"startsAt":"2027-01-13T00:15:00.000Z","revision":3}],"has_more":true}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/matches/{matchId}":{"patch":{"operationId":"ecrireResultat","tags":["Résultats"],"summary":"Écrit ou corrige le résultat d’une rencontre.","description":"Scope `write`. `expectedRevision` est OBLIGATOIRE : c’est la comparaison-et-échange qui empêche deux programmes d’écraser silencieusement le résultat l’un de l’autre. Une révision périmée rend 409 — relis la rencontre, ne réessaie pas à l’aveugle. Pas d’`Idempotency-Key` ici : le verrou de révision fait déjà ce travail, et deux mécanismes se contrediraient.","parameters":[{"name":"matchId","in":"path","required":true,"schema":{"type":"string"},"example":"f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResult"},"example":{"expectedRevision":3,"event":"COMPLETER","score":{"a":7,"b":5,"unitsPlayed":8}}}}},"responses":{"200":{"description":"La rencontre après écriture.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object"},"example":{"id":"f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c","status":"COMPLETED","scoreA":7,"scoreB":5,"resultSide":"A","sets":null,"revision":4}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Révision périmée, ou état qui n’admet pas cette commande.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Corps au-delà de 16 Kio (`BODY_TOO_LARGE`). La façade publique ne transporte aucun fichier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/registrations/{registrationId}/roster":{"get":{"operationId":"lireEffectif","tags":["Effectifs"],"summary":"L’effectif d’une inscription.","description":"Scope `members:read`. C’est la SEULE famille de routes qui rend des libellés de personnes, et une organisation peut la refuser à une clé sans lui retirer le reste. Aucun autre point de la façade — webhooks compris — ne les copie.","parameters":[{"name":"registrationId","in":"path","required":true,"schema":{"type":"string"},"example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"}],"responses":{"200":{"description":"L’effectif.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roster"},"example":{"registrationId":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","label":"Les Pierres","memberships":[{"personId":"3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a","label":{"kind":"NAME","value":"Camille Roy"},"role":"CAPTAIN","jerseyNumber":7,"endedAt":null}]}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"ajouterAuEffectif","tags":["Effectifs"],"summary":"Ajoute une personne à un effectif.","description":"Scope `members:write`.","parameters":[{"name":"registrationId","in":"path","required":true,"schema":{"type":"string"},"example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"example":"6a1f0c3e-creer-ligue-mardi","description":"Rejoue la réponse d’origine pendant 24 heures : un succès, ou un refus rendu pendant l’exécution. Un corps refusé avant (JSON mal formé, champ invalide) ne réserve pas la clé. Même clé et corps différent : 409 `IDEMPOTENCY_KEY_REUSED`. Une exécution encore en vol répond 409 `IDEMPOTENT_REQUEST_IN_FLIGHT`. Une 5xx n’est jamais figée : elle dit « réessaie ». Et si une exécution s’interrompt sans que son issue soit connue — une connexion perdue juste après le commit rend la même erreur qu’une perdue juste avant — le rejeu répond 409 `IDEMPOTENCY_OUTCOME_UNKNOWN` plutôt que de risquer un doublon : relis la ressource, puis reprends avec une clé neuve."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMembership"},"example":{"personId":"3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a","role":"CAPTAIN","jerseyNumber":7}}}},"responses":{"200":{"description":"L’effectif après ajout.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roster"},"example":{"registrationId":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","label":"Les Pierres","memberships":[{"personId":"3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a","label":{"kind":"NAME","value":"Camille Roy"},"role":"CAPTAIN","jerseyNumber":7,"endedAt":null}]}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejeu divergent de la clé d’idempotence, ou conflit d’état.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Corps au-delà de 16 Kio (`BODY_TOO_LARGE`). La façade publique ne transporte aucun fichier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events":{"get":{"operationId":"listerEvenements","tags":["Événements"],"summary":"Les événements structurels, du plus ancien au plus récent.","description":"Scope `read`. LE RATTRAPAGE : c’est ce qui évite « on a perdu deux jours de webhooks ». Le flux est exactement celui des webhooks — même liste blanche, même charge mince, mêmes identifiants (`webhook-id` vaut `id`). Aucun événement de personne, de gouvernance ou de paiement n’y figure.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"example":25,"description":"Taille de page. Au-delà de 100, la requête est refusée plutôt que rognée en silence."},{"name":"after","in":"query","required":false,"schema":{"type":"string"},"example":"5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f","description":"Curseur : l’identifiant du DERNIER élément de la page précédente. Un curseur inconnu est refusé — il ne se rattrape pas. Les trois listes paginées — événements, ligues, rencontres — suivent la MÊME règle : le parcours ordonne sur un numéro d’insertion interne qu’aucune modification ne déplace, et une page s’arrête avant toute écriture dont la transaction peut encore être en vol. Une ligne écrite par une transaction longue est donc RETARDÉE, jamais sautée : elle apparaît à sa place dès que sa transaction est commise, et exactement une fois. Renommer une ligue ou corriger une rencontre entre deux pages ne la fait ni disparaître ni revenir."}],"responses":{"200":{"description":"Une page d’événements.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"type":"object","required":["object","data","has_more"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"}}},"example":{"object":"list","data":[{"id":"8f6e4d2c-0b9a-4c7e-a5d3-1f9e7c5a3b0d","type":"match.completed","created":"2027-01-13T03:12:45.000Z","organization":"9d3f5a7c-2b1e-4c8d-a6f0-3e2d1c0b9a87","data":{"object":"match","id":"f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c","revision":4}}],"has_more":false}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events/{eventId}":{"get":{"operationId":"lireEvenement","tags":["Événements"],"summary":"Relit un événement reçu par webhook.","description":"Scope `read`. Un événement hors liste blanche est INTROUVABLE, pas interdit : il n’appartient pas au contrat public.","parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string"},"example":"8f6e4d2c-0b9a-4c7e-a5d3-1f9e7c5a3b0d"}],"responses":{"200":{"description":"L’événement.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"},"example":{"id":"8f6e4d2c-0b9a-4c7e-a5d3-1f9e7c5a3b0d","type":"match.completed","created":"2027-01-13T03:12:45.000Z","organization":"9d3f5a7c-2b1e-4c8d-a6f0-3e2d1c0b9a87","data":{"object":"match","id":"f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c","revision":4}}}}},"400":{"description":"Requête malformée (`INVALID_INPUT`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente, mal formée, révoquée ou expirée (`UNAUTHORIZED`). Les cookies de session ne valent rien ici.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clé n’a pas le scope requis (`FORBIDDEN`), ou l’abonnement de l’organisation ne permet pas cet accès (`ENTITLEMENT_DENIED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Ressource introuvable dans l’organisation de la clé (`NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Trop de requêtes (`RATE_LIMITED`), par adresse ou par clé. Attends le nombre de secondes de `Retry-After`, puis reprends avec un recul exponentiel bruité.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Indisponible. `LEAGUES_UNAVAILABLE` (panne ou délai de 10 s dépassé) porte `Retry-After: 30` ; une écriture peut alors ne pas être confirmée, vérifie son état avant de la répéter. `ENTITLEMENT_UNAVAILABLE` et `HOLDER_UNINITIALIZED` : droit commercial indisponible ou titulaire non initialisé.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfterUnavailable"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"cleDOrganisation":{"type":"http","scheme":"bearer","bearerFormat":"rk_live_…","description":"Clé d’organisation, émise depuis les réglages de l’organisation par un compte qui la gouverne. Le secret n’est montré qu’à l’émission. Portée par l’en-tête `Authorization` UNIQUEMENT : jamais en chaîne de requête, où elle finirait dans les journaux du serveur mandataire et les en-têtes `Referer`."}},"headers":{"RequestId":{"required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{8,64}$"},"description":"L’identifiant de CETTE requête, rendu sur toute réponse, succès compris, et repris dans `request_id` d’une erreur. Si tu en envoies un valide dans `X-Request-Id`, il est repris tel quel ; sinon un nouveau est tiré."},"IdempotentReplayed":{"required":false,"schema":{"type":"string","enum":["true"]},"description":"Présent, à `true`, quand la réponse est le rejeu d’une requête déjà exécutée avec la même `Idempotency-Key`."},"RetryAfter":{"required":true,"schema":{"type":"integer","minimum":1},"description":"Secondes à attendre avant de réessayer."},"RetryAfterUnavailable":{"required":false,"schema":{"type":"integer","minimum":1},"description":"Secondes à attendre avant de réessayer : `30`, présent quand le code vaut `LEAGUES_UNAVAILABLE`."}},"schemas":{"Error":{"type":"object","required":["error","code","request_id"],"properties":{"error":{"type":"string","description":"Message lisible, en français. Dérivé du seul code : il ne porte jamais d’identifiant interne."},"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","CONFLICT","INVALID_INPUT","IDEMPOTENCY_KEY_REUSED","IDEMPOTENT_REQUEST_IN_FLIGHT","IDEMPOTENCY_OUTCOME_UNKNOWN","ENTITLEMENT_DENIED","ENTITLEMENT_UNAVAILABLE","HOLDER_UNINITIALIZED","BODY_TOO_LARGE","WEBHOOK_VAULT_UNAVAILABLE","ROTATION_EN_COURS","LEAGUES_UNAVAILABLE","RATE_LIMITED"]},"request_id":{"type":"string","description":"L’identifiant de CETTE requête, également rendu en en-tête `X-Request-Id` sur toute réponse — succès compris. Recopie-le dans un ticket : c’est ce qui permet de retrouver ta requête dans nos journaux. Si tu en envoies un toi-même dans `X-Request-Id`, il est repris tel quel (8 à 64 caractères de `A-Za-z0-9_.:-`)."}}},"SportTerm":{"type":"object","required":["one","many","genre"],"properties":{"one":{"type":"string"},"many":{"type":"string"},"genre":{"type":"string","enum":["M","F"]}}},"SportCadence":{"type":"object","required":["unitsPerMatch","minutesPerUnit","matchesPerNight","scoreTarget","seasonWeeks"],"description":"Repères modifiables du profil ; les valeurs nulles ne prétendent aucun défaut.","properties":{"unitsPerMatch":{"type":["integer","null"],"minimum":1},"minutesPerUnit":{"type":["integer","null"],"minimum":1},"matchesPerNight":{"type":["integer","null"],"minimum":1},"scoreTarget":{"type":["integer","null"],"minimum":1},"seasonWeeks":{"type":["object","null"],"required":["min","max"],"properties":{"min":{"type":"integer","minimum":1},"max":{"type":"integer","minimum":1}}}}},"SportProfile":{"type":"object","required":["key","label","version","resourceKind","cadence","vocabulary"],"description":"Le profil sportif RÉSOLU de la ligue. `label` est le libellé du catalogue, ou le sport saisi par le club quand la clé vaut `AUTRE`. `version` est celle du catalogue qui a rendu ce profil : elle avance quand une valeur rendue change, jamais rétroactivement.","properties":{"key":{"type":"string","enum":["CURLING","CORNHOLE","HOCKEY_RECREATIF","VOLLEYBALL","PICKLEBALL","BALLE_MOLLE","AUTRE"]},"label":{"type":"string"},"version":{"type":"integer"},"resourceKind":{"type":"string","enum":["SHEET","COURT","FIELD","SURFACE"]},"cadence":{"$ref":"#/components/schemas/SportCadence"},"vocabulary":{"type":"object","required":["resource","unit","match","scoreUnit"],"description":"Les mots du sport : la ressource qu’un créneau réserve (feuille, planche, terrain), l’unité qu’une rencontre compte (bout, manche, période), ce qu’on joue et ce qu’on marque.","properties":{"resource":{"$ref":"#/components/schemas/SportTerm"},"unit":{"$ref":"#/components/schemas/SportTerm"},"match":{"$ref":"#/components/schemas/SportTerm"},"scoreUnit":{"$ref":"#/components/schemas/SportTerm"}}}}},"League":{"type":"object","required":["id","name","sport","sportLabel","profile","timeZone"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"sport":{"type":"string","enum":["CURLING","CORNHOLE","HOCKEY_RECREATIF","VOLLEYBALL","PICKLEBALL","BALLE_MOLLE","AUTRE"]},"sportLabel":{"type":["string","null"],"description":"Le sport saisi par le club quand `sport` vaut `AUTRE` ; `null` sinon."},"profile":{"$ref":"#/components/schemas/SportProfile"},"timeZone":{"type":"string"}}},"LeagueDetail":{"type":"object","required":["object","id","name","sport","sportLabel","profile","timeZone","cadences","seasons"],"properties":{"object":{"type":"string","const":"league"},"id":{"type":"string"},"name":{"type":"string"},"sport":{"type":"string","enum":["CURLING","CORNHOLE","HOCKEY_RECREATIF","VOLLEYBALL","PICKLEBALL","BALLE_MOLLE","AUTRE"]},"sportLabel":{"type":["string","null"]},"profile":{"$ref":"#/components/schemas/SportProfile"},"timeZone":{"type":"string"},"cadences":{"type":"array","items":{"$ref":"#/components/schemas/Cadence"}},"seasons":{"type":"array","items":{"$ref":"#/components/schemas/Season"}}}},"Cadence":{"type":"object","required":["id","key","label","position"],"properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"position":{"type":"integer"}}},"CreateLeague":{"type":"object","required":["name","sport","timeZone"],"properties":{"name":{"type":"string","maxLength":200},"sport":{"type":"string","enum":["CURLING","CORNHOLE","HOCKEY_RECREATIF","VOLLEYBALL","PICKLEBALL","BALLE_MOLLE","AUTRE"],"description":"Une clé du catalogue de profils sportifs. `AUTRE` pour un sport non préréglé."},"sportLabel":{"type":["string","null"],"maxLength":60,"description":"Le nom du sport, OBLIGATOIRE quand `sport` vaut `AUTRE` et refusé sinon."},"timeZone":{"type":"string","description":"Fuseau IANA, par exemple `America/Toronto`."}}},"Season":{"type":"object","required":["id","name","sportProfile","sportProfileVersion","resourceKind","cadence","vocabulary"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"cadenceId":{"type":"string"},"ordinal":{"type":"integer"},"status":{"type":"string","enum":["SETUP","REGULAR","PLAYOFFS","CLOSED","ABANDONED"]},"sportProfile":{"type":["string","null"],"description":"Clé littérale figée à la création ; null avant le catalogue. Une clé future reste rendue."},"sportProfileVersion":{"type":["integer","null"],"minimum":1},"resourceKind":{"type":"string","enum":["SHEET","COURT","FIELD","SURFACE"]},"cadence":{"$ref":"#/components/schemas/SportCadence"},"vocabulary":{"type":"object","required":["resource","unit","match","scoreUnit"],"properties":{"resource":{"$ref":"#/components/schemas/SportTerm"},"unit":{"$ref":"#/components/schemas/SportTerm"},"match":{"$ref":"#/components/schemas/SportTerm"},"scoreUnit":{"$ref":"#/components/schemas/SportTerm"}}}}},"CreateSeason":{"type":"object","required":["name","cadenceId"],"properties":{"name":{"type":"string","maxLength":200},"cadenceId":{"type":"string"},"startsAt":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"}}},"Category":{"type":"object","required":["id","seasonId","name","phase","revision"],"properties":{"id":{"type":"string"},"seasonId":{"type":"string"},"name":{"type":"string"},"phase":{"type":"string","enum":["SETUP","REGULAR","PLAYOFFS","CLOSED","ABANDONED"]},"revision":{"type":"integer"},"policyRevision":{"type":"integer"},"matchCount":{"type":"integer"},"unplayedMatchCount":{"type":"integer"},"unplayedRegularMatchCount":{"type":"integer"},"registrationCapacity":{"type":["integer","null"]}}},"StandingRow":{"type":"object","properties":{"leagueTeamId":{"type":"string"},"name":{"type":"string"},"rank":{"type":"integer"},"tied":{"type":"boolean"},"played":{"type":"integer"},"wins":{"type":"integer"},"draws":{"type":"integer"},"losses":{"type":"integer"},"scoreFor":{"type":"integer"},"scoreAgainst":{"type":"integer"},"diff":{"type":"integer"},"gamePointsFor":{"type":"integer","description":"Points de jeu marqués, cumulés sur le détail des sets. 0 hors de la forme de résultat SETS."},"gamePointsAgainst":{"type":"integer"},"points":{"type":"integer"}}},"Match":{"type":"object","required":["id","revision"],"properties":{"id":{"type":"string"},"categoryId":{"type":"string"},"status":{"type":"string"},"teamAId":{"type":["string","null"]},"teamBId":{"type":["string","null"]},"scoreA":{"type":["integer","null"]},"scoreB":{"type":["integer","null"]},"sets":{"type":["array","null"],"description":"Détail des sets sous la forme de résultat SETS ; null ailleurs, tableau vide sur un forfait. scoreA et scoreB portent alors les SETS gagnés.","items":{"$ref":"#/components/schemas/SetScore"}},"startsAt":{"type":["string","null"],"format":"date-time"},"revision":{"type":"integer","description":"Le verrou optimiste à annoncer dans `expectedRevision`."}}},"MatchResult":{"type":"object","required":["expectedRevision","event"],"properties":{"expectedRevision":{"type":"integer"},"event":{"type":"string","enum":["COMPLETER","ATTRIBUER","CORRIGER_SCORE","CORRIGER_ATTRIBUTION"]},"score":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"},"unitsPlayed":{"type":"integer"}}},"sets":{"type":"array","maxItems":15,"description":"Détail des sets, exigé sous la forme de résultat SETS et refusé ailleurs. Le score de match ne s’envoie pas : il se dérive du détail. Une politique ne peut pas exiger plus de 15 sets, donc aucun résultat n'en porte davantage.","items":{"$ref":"#/components/schemas/SetScore"}},"resultSide":{"type":["string","null"],"enum":["A","B","DRAW",null]},"forfeitingSide":{"type":["string","null"],"enum":["A","B","BOTH",null]},"outcome":{"type":["string","null"],"enum":["PLAYED","CONCEDED","FORFEIT","DOUBLE_FORFEIT","ABANDONED",null]},"reason":{"type":["string","null"],"minLength":1,"maxLength":500}}},"SetScore":{"type":"object","required":["a","b"],"properties":{"a":{"type":"integer"},"b":{"type":"integer"}}},"Roster":{"type":"object","properties":{"registrationId":{"type":"string"},"label":{"type":"string"},"memberships":{"type":"array","items":{"type":"object"}}}},"AddMembership":{"type":"object","required":["personId"],"properties":{"personId":{"type":"string"},"role":{"type":"string","enum":["PLAYER","CAPTAIN","COACH"]},"jerseyNumber":{"type":["integer","null"],"minimum":0,"maximum":999}}},"Event":{"type":"object","required":["id","type","created","organization","data"],"properties":{"id":{"type":"string","description":"Vaut l’en-tête `webhook-id` de la livraison correspondante."},"type":{"type":"string","enum":["league.created","season.created","season.closed","season.reopened","season.abandoned","season.renewed","category.created","category.updated","registration.submitted","registration.withdrawn","registration.reinstated","schedule.published","match.rescheduled","match.completed","match.awarded","match.corrected","match.cancelled","match.voided","series.opened","series.updated"]},"created":{"type":"string","format":"date-time"},"organization":{"type":"string"},"data":{"type":"object","description":"CHARGE MINCE, toujours : un objet, son identifiant, sa révision. Jamais un nom, une adresse ni une date de naissance — une URL de webhook est un point de sortie vers un tiers, souvent en clair dans SES journaux. Le consommateur relit l’état courant, il ne rejoue pas un état vieux de six heures.","properties":{"object":{"type":"string"},"id":{"type":"string"},"revision":{"type":["integer","null"]},"previous":{"type":"object"},"scheduled":{"type":"object","description":"`match.rescheduled` UNIQUEMENT — la place visée, quand `previous` porte celle qu’elle remplace : `resourceId`, `startsAt`, `endsAt`. C’est le seul événement à porter deux valeurs, parce que c’est le seul fait qu’un consommateur ne peut pas reconstituer sans avoir gardé son propre état. Il est émis à la PUBLICATION de l’horaire, une fois par rencontre dont la place publiée a changé : un brouillon de déplacement n’a été communiqué à personne et ne s’annonce donc jamais."}}}}}}}}