← Retour au guide développeur

Référence générée · v1.0.0

Référence de l'API publique

Générée automatiquement depuis notre spécification OpenAPI — toujours à jour avec les endpoints réellement servis. Importez la spec dans Postman ou votre outil de génération de code.

Base URL : https://owwomenscup.frTélécharger la spec OpenAPI (JSON)

Guide

API publique de la plateforme : lectures REST anonymes (GET /api/public/v1/*), une écriture REST authentifiée (POST /api/public/v1/matches/{id}/result) et un endpoint GraphQL (POST /api/graphql, décrit plus bas : il n'a pas d'entrée dans la liste des endpoints). Les surfaces bot, admin et cron sont internes et absentes d'ici. Les routes free-players, team-openings, newsletter et scrim-requests servent les formulaires du site (captcha) : elles ne sont pas destinées à une intégration.

Quel espace répond

La plateforme sert plusieurs espaces (organisations). Une réponse qui vient du mauvais espace n'échoue pas : elle est valide, et fausse.

  • Lectures REST (GET /api/public/v1/*) : ajouter ?tenant=<slug> pour viser un espace, par exemple /api/public/v1/tournaments?tenant=cup-estivale. SANS ce paramètre, sur https://owwomenscup.fr, c'est l'espace Women's Cup qui répond, jeton ou pas : ces lectures IGNORENT le jeton. Un slug inconnu ou un espace désactivé retombent aussi sur Women's Cup, sans erreur. (Un espace doté d'un domaine propre est aussi reconnu quand l'API est appelée sur ce domaine.)
  • Écriture REST et GraphQL avec jeton : l'espace est celui du jeton, fixé à son émission. Aucun paramètre ni en-tête ne peut le déplacer ; ?tenant= n'y a aucun effet.
  • GraphQL sans jeton, ou avec un jeton refusé : espace Women's Cup, sans erreur.

Vérification simple : comparer un nom d'équipe ou de tournoi renvoyé avec le back-office de l'espace visé.

Cache

Les lectures REST posent Cache-Control: public, s-maxage=N (N indiqué sur chaque endpoint : 30 à 3600 s). Le cache du CDN est tenu par URL complète, paramètres de query compris (?tenant=, filtres, pagination, format) : deux URL qui diffèrent par un paramètre ont chacune leur réponse. Une donnée modifiée peut donc mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

POST /api/graphql et l'écriture REST ne posent aucun en-tête de cache public (no-store pour l'écriture).

Authentification et clés

  • En-tête : Authorization: Bearer pk_live_<64 hex>. Détail dans le schéma de sécurité PublicApiToken.
  • Une clé est émise par le staff et rattachée à UN espace. Depuis l'écran /admin/api-tokens, c'est l'espace ACTIF du sélecteur du back-office qui la reçoit : le vérifier avant d'émettre. L'owner de la plateforme peut aussi émettre pour un espace nommé.
  • Affichée une seule fois. Révocable. Expiration optionnelle : une clé révoquée ou expirée répond 401 UNAUTHORIZED en REST, comme une clé inconnue — et passe en anonyme sur GraphQL.
  • Portées resource:action, sans implication entre elles. Aujourd'hui, seule matches:write est exigée ; aucune route ne contrôle une portée :read.
  • Plan de l'espace : l'écriture exige la capacité apiWrite (plans Circuit, Éditeur, Fondation) ; sinon 403 plan_required — valeur du champ error, pas de code. Une clé partenaire (comp) n'est jamais soumise au plan ni au quota. Les lectures REST et les requêtes GraphQL ne contrôlent ni plan, ni portée, ni quota.

Limites de débit

  • Lectures REST : 120 requêtes par minute par IP, compteur distinct par endpoint. 429 avec Retry-After: 60 ; selon le limiteur qui répond, le corps a ou non code: RATE_LIMITED.
  • Écriture REST : 30 par minute par IP, puis 15 par minute par jeton (ACTOR_RATE_LIMIT), puis le quota du plan : Circuit 120 par minute et 500 000 par mois, Éditeur et Fondation illimités (RATE_LIMITED ou QUOTA_EXCEEDED, avec X-RateLimit-Scope et X-RateLimit-Limit). Le quota est aussi consommé par la mutation GraphQL. Clé partenaire : pas de quota.
  • GraphQL : 600 requêtes/min par IP (requêtes et mutation) ; au-delà, HTTP 429 avec Retry-After et errors[].extensions.code = RATE_LIMITED.

Erreurs

REST : { "error": "…", "code": "…" }. Tester code, jamais error (texte libre). Liste complète et sens : schéma PublicApiErrorCode. Deux corps sans code : le 429 du limiteur par IP en mémoire et le 403 plan_required (schéma PublicPlanDenial). fields détaille les erreurs de validation (INVALID_BODY, INVALID_QUERY).

Idempotence

L'écriture REST honore Idempotency-Key (200 caractères au plus) : une réponse 2xx est rejouée pendant 5 minutes pour la même clé ET le même corps, avec Idempotency-Replay: true. La mutation GraphQL n'a pas d'idempotence.

GraphQL — POST /api/graphql

  • Corps JSON { "query": "…", "variables": { … } }. Utiliser POST.
  • Espace : celui du jeton Authorization: Bearer pk_live_…. Sans jeton, ou si le jeton est inconnu, révoqué, expiré ou mal formé : espace Women's Cup, SANS erreur. Vérifier le résultat : par exemple { tournaments { count items { slug name } } } doit lister les tournois de votre espace.
  • Requêtes : tournaments(status, game, limit, offset) (liste paginée, limit ramené dans [1, 100], count = total), tournament(idOrSlug), match(id), team(idOrSlug). Champs en snake_case, mêmes projections et mêmes règles de visibilité que le REST (tournois published / running / completed ; matchs pending / ongoing / finished). Les requêtes ne contrôlent ni plan, ni portée, ni quota.
  • Mutation : reportMatchResult(matchId, team1Score, team2Score), portée matches:write, mêmes effets que l'écriture REST. Pas de mode maintenance, pas d'idempotence.
  • Codes (errors[].extensions.code) : UNAUTHENTICATED (pas de jeton valide), FORBIDDEN (plan insuffisant — reason: plan_required et requiredCapability — ou portée manquante), RATE_LIMITED et QUOTA_EXCEEDED (avec retryAfterSec et limit), BAD_USER_INPUT (score hors [0, 99], match bye ou sans ses deux équipes), NOT_FOUND, CONFLICT (match déjà clôturé), INTERNAL_SERVER_ERROR. Toute autre erreur serveur est masquée derrière un message générique.
  • Profondeur de requête : 8 au plus. Introspection et GraphiQL désactivés en production : le schéma est celui décrit ici.

Webhooks sortants

Un espace abonne une URL depuis /admin/webhooks et reçoit ses événements en POST signé. Catalogue, signature et règles de livraison : GET /api/public/webhook-events. match.forfeit figure au catalogue mais n'est émis par aucun code aujourd'hui.

Compatibilité et versionnement

  • Aucune politique de versionnement n'est formalisée pour /api/public/v1/*. Constaté jusqu'ici : des champs AJOUTÉS aux réponses, et un champ RETIRÉ dans /v1 même (tenant_id des ligues, 2026-09-15).
  • Les schémas de réponse publiés sont fermés (additionalProperties: false) : ils décrivent la réponse du jour. Un client généré ne doit pas rejeter un champ inconnu, sans quoi le moindre ajout le casse.
  • Les changements de contrat sont datés dans docs/PUBLIC_API_CONTRACT.md (section « Changements de contrat »).

Authentification

PublicApiToken

Jeton d'API d'un espace : Authorization: Bearer pk_live_<64 hex>, distinct de la clé bot.

ESPACE. Le jeton est rattaché à UN espace, fixé à l'émission ; c'est lui qui détermine l'espace des requêtes qu'il authentifie, et aucun paramètre ni en-tête ne peut le déplacer. Deux chemins d'émission, qui ne visent pas le même espace :

  • POST /api/admin/api-tokens (écran /admin/api-tokens) émet pour l'espace ACTIF du sélecteur du back-office de l'émetteur — vérifier ce sélecteur avant d'émettre ;
  • POST /api/admin/tenants/{id}/api-tokens (owner de la plateforme) émet pour l'espace désigné par {id}, écrit dans l'URL.

OÙ IL SERT. Aujourd'hui, deux surfaces seulement lisent ce jeton : POST /api/public/v1/matches/{id}/result et POST /api/graphql (où il fixe l'espace des requêtes et autorise la mutation). Les lectures REST GET /api/public/v1/* IGNORENT l'en-tête Authorization : le jeton n'y change ni l'espace, ni les limites, ni le cache.

VALIDITÉ. Affiché en clair une seule fois, à l'émission ; seul le préfixe pk_live_xxxxxx reste visible ensuite. Refusé en 401 UNAUTHORIZED s'il est inconnu, révoqué (DELETE /api/admin/api-tokens/{id} ou DELETE /api/admin/tenants/{id}/api-tokens?tokenId=) ou expiré (expires_at, durée optionnelle choisie à l'émission), sans distinguer ces cas. Sur GraphQL, un jeton refusé ne produit AUCUNE erreur : la requête est traitée comme anonyme (voir l'introduction).

PORTÉES. resource:action (tournaments, matches, teams, players × read, write), sans implication entre elles. Seule matches:write est exigée par une route aujourd'hui ; aucune route ne contrôle une portée :read.

Endpoints

get/api/public/free-playersSans auth

Liste anonymisee des joueuses cherchant une equipe.

Vitrine publique du marche des joueuses libres (lot 1 acquisition). Ne renvoie AUCUN moyen de contact — c'est une preuve qu'il y a du monde, pas un carnet d'adresses. Les annonces perimees (60 j) sont exclues.

Réponses

StatutSchémaDescription
200object

Liste anonymisee.

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

post/api/public/free-playersSans auth

Se signaler comme joueuse libre — SANS COMPTE. Anti-bot captcha + honeypot.

Cree (ou rafraichit) une fiche source='web' dans free_players, sans exiger de compte : c'est tout l'objet du lot 1 — une joueuse sans equipe n'avait aucun chemin sur le site. Une re-soumission avec le meme email met a jour la fiche et repousse sa peremption. Reponse toujours generique { success: true } (enumeration-safe). Un honeypot rempli est silencieusement absorbe (200). Emet l'event bot free_player.registered a la premiere inscription.

Corps de la requête

PublicFreePlayerSignupInput

Réponses

StatutSchémaDescription
200object

Toujours renvoye tant que la requete est bien formee (succes generique).

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/free-players/removeSans auth

Décrit la fiche visée par un lien de retrait (ne supprime rien).

Sert la page de confirmation /rejoindre/retrait : elle doit montrer CE qu'on s'apprête à retirer. La separation GET/POST n'est pas cosmetique — les clients mail et antivirus pre-visitent les liens d'un email, un GET destructeur ferait disparaitre des fiches sans clic humain. Token HMAC recu par email a l'inscription (aucun compte requis).

Paramètres

ParamètreEmplacementTypeDescription
tokenrequisrequêtestring

Réponses

StatutSchémaDescription
200object

Fiche trouvee.

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

404Error

Ressource introuvable (UUID inconnu, route inexistante).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

post/api/public/free-players/removeSans auth

Retire définitivement une fiche « joueuse libre ».

Porte de sortie autonome du parcours /rejoindre : l'inscription se fait SANS compte, donc sans cette route une joueuse devrait ecrire au staff pour disparaitre d'une liste publique qu'elle a elle-meme alimentee. Token invalide et fiche introuvable renvoient le MEME message : les distinguer transformerait la route en oracle d'existence.

Paramètres

ParamètreEmplacementTypeDescription
tokenrequêtestring

Accepte aussi dans le corps JSON : { "token": "..." }.

Réponses

StatutSchémaDescription
200object

Fiche retiree.

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

404Error

Ressource introuvable (UUID inconnu, route inexistante).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/newsletter/confirmSans auth

Confirme une inscription newsletter (double opt-in) via le lien email.

Cible du lien de confirmation. Passe l'abonne a confirmed puis redirige (302) vers /newsletter/merci. Token manquant / invalide / introuvable → redirige vers /newsletter/merci?status=invalid. Pas de captcha (clic sur un lien).

Paramètres

ParamètreEmplacementTypeDescription
tokenrequisrequêtestring

Le confirm_token recu par email.

Réponses

StatutSchémaDescription
302—

Redirection vers la page de remerciement (ou ?status=invalid).

post/api/public/newsletter/subscribeSans auth

Inscription newsletter (double opt-in) — anti-bot captcha + honeypot.

Enregistre (ou re-arme) un abonne pending avec un confirm_token aleatoire et envoie l'email de confirmation. Reponse toujours generique { success: true } (enumeration-safe) : ne revele jamais si l'adresse existait deja. Un honeypot rempli est silencieusement absorbe (200).

Corps de la requête

PublicNewsletterSubscribeInput

Réponses

StatutSchémaDescription
200object

Toujours renvoye tant que la requete est bien formee (succes generique).

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/openapiSans auth

Spécification OpenAPI publique (machine-readable).

Spec OpenAPI 3.1 filtrée à la surface publique (/api/public/*), dérivée de la spec complète (fragments docs/openapi/). À importer dans Postman ou un générateur de code. Version rendue lisible : /developpeurs/reference.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP (le 429 n'a pas de code). Cache : public, max-age=300, s-maxage=3600.

Paramètres

ParamètreEmplacementTypeDescription
formatrequêteenum(json | yaml)

yaml pour la variante YAML (text/yaml), toute autre valeur donne du JSON.

Réponses

StatutSchémaDescription
200object

Document OpenAPI (JSON par défaut, YAML si demandé).

405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
post/api/public/scrim-requestsSans auth

Propose un scrim public a une de nos equipes (anti-bot captcha + honeypot).

Corps de la requête

PublicScrimRequestInput

Réponses

StatutSchémaDescription
201object

Demande de scrim enregistree.

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

404Error

Equipe cible introuvable.

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/team-openingsSans auth

Liste anonymisee des equipes qui cherchent une joueuse.

Miroir de /api/public/free-players : la vitrine des equipes qui recrutent. Ne renvoie AUCUN moyen de contact — repondre a une annonce passe par /api/team-openings/contact, derriere un compte. Les annonces perimees (60 j) sont exclues. Cache CDN 60 s.

Paramètres

ParamètreEmplacementTypeDescription
tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Liste anonymisee.

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

post/api/public/team-openingsSans auth

Publier une annonce de recrutement — SANS COMPTE. Anti-bot captcha + honeypot.

Cree (ou rafraichit) une annonce source='web' dans team_openings, sans exiger de compte : une equipe qui se monte n'a souvent pas encore d'existence sur le site. Une re-soumission avec le meme email met a jour l'annonce et repousse sa peremption. Reponse toujours generique { success: true } (enumeration-safe). Un honeypot rempli est silencieusement absorbe (200). Emet l'event bot team_opening.published a la premiere publication.

Corps de la requête

PublicTeamOpeningInput

Réponses

StatutSchémaDescription
200object

Toujours renvoye tant que la requete est bien formee (succes generique).

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/team-openings/removeSans auth

Decrit l'annonce visee par un lien de retrait (ne supprime rien).

Sert la page de confirmation /recrutement/retrait : elle doit montrer CE qu'on s'apprete a retirer. La separation GET/POST n'est pas cosmetique — les clients mail et antivirus pre-visitent les liens d'un email, un GET destructeur ferait disparaitre des annonces sans clic humain. Token HMAC recu par email a la publication (aucun compte requis).

Paramètres

ParamètreEmplacementTypeDescription
tokenrequisrequêtestring

Réponses

StatutSchémaDescription
200object

Annonce trouvee.

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

404Error

Ressource introuvable (UUID inconnu, route inexistante).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

post/api/public/team-openings/removeSans auth

Retire definitivement une annonce de recrutement.

Porte de sortie autonome du parcours : la publication se fait SANS compte, donc sans cette route une equipe au complet devrait ecrire au staff pour disparaitre — et en attendant, chaque joueuse qui repond perd son temps. Token invalide et annonce introuvable renvoient le MEME message : les distinguer transformerait la route en oracle d'existence.

Paramètres

ParamètreEmplacementTypeDescription
tokenrequêtestring

Accepte aussi dans le corps JSON : { "token": "..." }.

Réponses

StatutSchémaDescription
200object

Annonce retiree.

400Error

Requête invalide (validation zod, JSON mal formé, etc.).

404Error

Ressource introuvable (UUID inconnu, route inexistante).

429Error

Rate-limit dépassé (par-actor ou par-route).

En-têtes

  • Retry-After

    Secondes avant retry.

get/api/public/v1/leaderboardSans auth

Classement Glicko-2 public des joueuses.

Joueuses de l'espace servi ayant joué au moins un match classé, triées par rating décroissant (départage stable par identifiant). rank est la position absolue (offset + position dans la page). ATTENTION : pagination.count est le nombre d'éléments de CETTE page, pas le total.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

limitrequêteinteger

Ramené dans [1, 100] ; une valeur non numérique vaut 50.

offsetrequêteinteger

Ramené à 0 au minimum.

Réponses

StatutSchémaDescription
200object

Page du classement + pagination.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": [
    {
      "userId": "8d7c6b5a-4938-4271-b6a5-948372615049",
      "displayName": "Nova",
      "battleTag": "Nova#21456",
      "avatarUrl": null,
      "teamName": "Les Aurores",
      "teamSlug": "les-aurores",
      "teamLogoUrl": "https://example.org/logos/aurores.png",
      "rating": 1623.4,
      "rd": 71.2,
      "gamesPlayed": 12,
      "wins": 8,
      "losses": 4,
      "rank": 1
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "count": 1
  }
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/leagues/{slug}Sans auth

Détail public d'une league (standings + tournois liés).

Ligue publique de l'espace servi avec son classement, ses tournois et ses scrims comptés. 404 si la ligue est inconnue, privée ou en brouillon.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 120 s.

Paramètres

ParamètreEmplacementTypeDescription
slugrequischeminstring

Slug de la ligue (pas d'identifiant UUID).

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Détail league.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "league": {
      "id": "4b3a2918-0f7e-4d6c-95b4-a39281706f5e",
      "name": "Saison 2026",
      "slug": "saison-2026",
      "description": null,
      "game": "overwatch",
      "status": "active",
      "start_date": "2026-01-10",
      "end_date": "2026-12-20",
      "points_table": {
        "1": 100,
        "2": 70,
        "3": 50
      },
      "is_public": true,
      "created_at": "2025-12-01T10:00:00+00:00",
      "updated_at": "2026-09-01T08:30:00+00:00"
    },
    "standings": [
      {
        "teamId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "teamName": "Les Aurores",
        "teamSlug": "les-aurores",
        "logoUrl": "https://example.org/logos/aurores.png",
        "points": 170,
        "tournamentsCounted": 2,
        "scrimsCounted": 1,
        "bestRank": 1,
        "rank": 1
      }
    ],
    "tournaments": [
      {
        "id": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
        "name": "Coupe d'automne 2026",
        "slug": "coupe-automne-2026",
        "weight": 1
      }
    ],
    "scrims": [
      {
        "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2190",
        "name": "Aurores vs Nébuleuse",
        "slug": null,
        "weight": 0.5,
        "team1Name": "Les Aurores",
        "team2Name": "Nébuleuse",
        "team1Score": 3,
        "team2Score": 1,
        "scheduledDate": "2026-09-12T19:00:00+00:00"
      }
    ]
  }
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/leaguesSans auth

Liste des leagues publiques.

Ligues de l'espace servi marquées publiques et hors brouillon, triées par date de création décroissante. Pas de pagination.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 120 s.

Paramètres

ParamètreEmplacementTypeDescription
tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Leagues publiques.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": [
    {
      "id": "4b3a2918-0f7e-4d6c-95b4-a39281706f5e",
      "name": "Saison 2026",
      "slug": "saison-2026",
      "description": null,
      "game": "overwatch",
      "status": "active",
      "start_date": "2026-01-10",
      "end_date": "2026-12-20",
      "points_table": {
        "1": 100,
        "2": 70,
        "3": 50
      },
      "is_public": true,
      "created_at": "2025-12-01T10:00:00+00:00",
      "updated_at": "2026-09-01T08:30:00+00:00"
    }
  ]
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
post/api/public/v1/matches/{id}/resultToken requis

Pose le score final d'un match (écriture publique authentifiée).

Écriture AUTORITAIRE : pas de consensus des capitaines. Le match passe en finished, le vainqueur est propagé dans le bracket et les notifications partent, comme une saisie du back-office.

ESPACE. Celui du jeton : un match d'un autre espace répond 404, comme un match inconnu. Aucun paramètre ni en-tête ne change l'espace.

CONTRÔLES, dans l'ordre (le premier qui échoue répond) : méthode (405) → limite par IP, 30/min (429 sans code) → base disponible (503) → jeton (401) → limite par jeton, 15/min (429 ACTOR_RATE_LIMIT) → plan de l'espace (403 plan_required, sauf clé partenaire) → portée matches:write (403 INSUFFICIENT_SCOPE) → quota du plan (429 RATE_LIMITED / QUOTA_EXCEEDED, sauf clé partenaire) → maintenance (503) → validation de id puis du corps (400) → rejeu d'idempotence → match (404, 400 bye ou équipes manquantes, 409 déjà clôturé) → finalisation (500 si elle échoue).

IDEMPOTENCE. Envoyer Idempotency-Key : une réponse 2xx est rejouée pendant 5 minutes pour la même clé ET le même corps (en-tête Idempotency-Replay: true). Un corps différent avec la même clé est traité comme une nouvelle requête ; les erreurs ne sont pas mises en cache.

PAS DE CORS : un appel depuis un navigateur tiers échoue au preflight. Appeler depuis un serveur. Réponses Cache-Control: no-store.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring <uuid>

Id (UUID) du match. Non-UUID → 400 INVALID_QUERY.

idempotency-keyen-têtestring

Clé d'idempotence client (≤ 200 caractères après trim ; une clé vide ou plus longue est IGNORÉE, sans erreur). Une réponse 2xx est mise en cache 5 minutes et rejouée (en-tête Idempotency-Replay: true) pour la même route, la même clé ET le même corps : un corps différent est traité comme une nouvelle requête. Les erreurs ne sont pas mises en cache. Portée : espace de la clé (bot, public) ou membre du staff et espace actif (admin). Voir BOT_API_CONTRACT.md §Idempotency.

Corps de la requête

PropriétéTypeDescription
team1Scorerequisinteger
team2Scorerequisinteger
Exemple
{
  "team1Score": 2,
  "team2Score": 1
}

Réponses

StatutSchémaDescription
200object

Match finalisé.

En-têtes

  • Idempotency-Replay

    true quand la réponse est rejouée depuis le cache d'idempotence (même Idempotency-Key ET même corps dans les 5 minutes). Absent sinon.

  • X-RateLimit-Limit

    Limite de la fenêtre du plan (sur un 429 de quota : limite dépassée ; sur un succès : limite par minute). Absent pour une clé partenaire (comp) et pour les plans illimités.

  • X-RateLimit-Remaining

    Requêtes restantes dans la minute pour le plan (succès uniquement). Absent pour une clé partenaire (comp) et pour les plans illimités.

Exemple
{
  "data": {
    "matchId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
    "status": "finished",
    "team1Score": 2,
    "team2Score": 1,
    "winnerTeamId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b"
  }
}
400PublicApiError

Validation refusée (INVALID_QUERY, INVALID_BODY, avec fields) ou match inapplicable (BAD_REQUEST : bye, équipes non assignées).

Exemple — Score hors bornes
{
  "error": "Too big: expected number to be <=99",
  "code": "INVALID_BODY",
  "fields": {
    "team1Score": [
      "Too big: expected number to be <=99"
    ]
  }
}
Exemple — Match sans ses deux équipes
{
  "error": "Match incomplet (équipes non assignées)",
  "code": "BAD_REQUEST"
}
401PublicApiError

Jeton absent, mal formé, inconnu, révoqué ou expiré — UNAUTHORIZED. Les cinq cas renvoient la même réponse.

Exemple
{
  "error": "Invalid or missing API token.",
  "code": "UNAUTHORIZED"
}
403PublicPlanDenial | PublicApiError

Deux corps possibles, évalués dans cet ordre : plan insuffisant (PublicPlanDenial, sans code, jamais pour une clé partenaire), puis portée manquante (INSUFFICIENT_SCOPE).

Exemple — Plan insuffisant
{
  "error": "plan_required",
  "message": "Cette clé API en écriture nécessite le plan Circuit. Le plan Régie n'ouvre que la lecture. Mettez à niveau votre abonnement pour écrire via l'API.",
  "requiredCapability": "apiWrite"
}
Exemple — Portée manquante
{
  "error": "Token lacks required scope 'matches:write'.",
  "code": "INSUFFICIENT_SCOPE"
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que POST — METHOD_NOT_ALLOWED, en-tête Allow: POST.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
409PublicApiError

Match déjà clôturé (finished, walkover ou cancelled) — CONFLICT.

Exemple
{
  "error": "Match déjà clôturé (status=finished). Contactez le staff pour modifier.",
  "code": "CONFLICT"
}
429PublicApiError

Trois limites successives : par IP (corps sans code), par jeton (ACTOR_RATE_LIMIT), puis quota du plan — débit par minute (RATE_LIMITED) ou quota mensuel (QUOTA_EXCEEDED), avec X-RateLimit-Scope et X-RateLimit-Limit. Le quota du plan laisse passer si son compteur est indisponible et ne s'applique pas à une clé partenaire (comp).

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

  • X-RateLimit-Scope

    Fenêtre du quota de plan dépassée : minute (débit) ou month (quota mensuel). Absent des 429 du limiteur par IP ou par token.

  • X-RateLimit-Limit

    Limite de la fenêtre du plan (sur un 429 de quota : limite dépassée ; sur un succès : limite par minute). Absent pour une clé partenaire (comp) et pour les plans illimités.

Exemple — Limite par IP (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Limite par jeton
{
  "error": "Trop de requêtes pour ton compte. Réessaye plus tard.",
  "code": "ACTOR_RATE_LIMIT"
}
Exemple — Débit du plan
{
  "error": "API rate limit exceeded.",
  "code": "RATE_LIMITED"
}
Exemple — Quota mensuel du plan
{
  "error": "Monthly API quota exceeded for this plan.",
  "code": "QUOTA_EXCEEDED"
}
500PublicApiError

Erreur serveur — INTERNAL, y compris l'échec de la finalisation du score (dont une finalisation concurrente du même match). Relire le match (GET /api/public/v1/matches/{id}) avant de réessayer.

Exemple
{
  "error": "Échec de la finalisation : …",
  "code": "INTERNAL"
}
503PublicApiError

Écritures gelées pendant une maintenance (MAINTENANCE_MODE, Retry-After: 60), ou base indisponible (INTERNAL, sans Retry-After).

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Maintenance
{
  "error": "Site en maintenance, écritures temporairement désactivées.",
  "code": "MAINTENANCE_MODE"
}
Exemple — Base indisponible
{
  "error": "Service unavailable.",
  "code": "INTERNAL"
}
get/api/public/v1/matches/{id}Sans auth

Détail d'un match public + games (map par map).

Match de l'espace servi et ses parties, triées par map_order.

Seuls les statuts pending, ongoing et finished sont servis : un match walkover, disputed, postponed ou cancelled renvoie 404, comme un match inconnu.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 30 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring <uuid>

Id (UUID) du match. Un slug n'est PAS accepté (400).

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Détail match.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
    "stage_id": "9b8c7d6e-5f4a-4b3c-a2d1-0e9f8a7b6c5d",
    "round_number": 1,
    "bracket_side": "wb",
    "team1_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
    "team1_name": "Les Aurores",
    "team1_logo_url": "https://example.org/logos/aurores.png",
    "team2_id": "2f3e4d5c-6b7a-4899-8a1b-2c3d4e5f6a7b",
    "team2_name": "Nébuleuse",
    "team2_logo_url": null,
    "team1_score": 2,
    "team2_score": 1,
    "winner_team_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
    "status": "finished",
    "scheduled_at": "2026-10-03T18:00:00+00:00",
    "games": [
      {
        "map_name": "Lijiang Tower",
        "map_order": 1,
        "team1_score": 2,
        "team2_score": 0,
        "winner_team_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b"
      },
      {
        "map_name": "King's Row",
        "map_order": 2,
        "team1_score": 2,
        "team2_score": 3,
        "winner_team_id": "2f3e4d5c-6b7a-4899-8a1b-2c3d4e5f6a7b"
      },
      {
        "map_name": "Circuit Royal",
        "map_order": 3,
        "team1_score": 1,
        "team2_score": 0,
        "winner_team_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b"
      }
    ]
  }
}
400PublicApiError

Identifiant invalide (UUID exigé) — BAD_REQUEST.

Exemple
{
  "error": "Invalid match id",
  "code": "BAD_REQUEST"
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/players/{userId}Sans auth

Profil public d'une joueuse (rating, historique, H2H, achievements).

Profil de la joueuse dans l'espace servi. Une joueuse qui n'a encore joué aucun match classé a quand même un profil si elle figure sur un roster : player.unrated vaut alors true, player.rank vaut null et les chiffres de rating valent 0 (ne pas les afficher). 404 si la joueuse n'est connue ni du classement ni d'un roster de l'espace.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
userIdrequischeminstring <uuid>

Id (UUID) de la joueuse. Un slug ou un pseudo n'est PAS accepté (400).

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Profil joueur.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "player": {
      "userId": "8d7c6b5a-4938-4271-b6a5-948372615049",
      "displayName": "Nova",
      "battleTag": "Nova#21456",
      "avatarUrl": null,
      "twitch": "nova_ow",
      "unrated": false,
      "rating": 1623.4,
      "rd": 71.2,
      "volatility": 0.06,
      "peakRating": 1650.1,
      "gamesPlayed": 12,
      "wins": 8,
      "losses": 4,
      "rank": 3
    },
    "history": [
      {
        "matchId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
        "tournamentId": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
        "occurredAt": "2026-10-03T19:12:00+00:00",
        "ratingBefore": 1598.9,
        "ratingAfter": 1623.4,
        "result": "win",
        "opponentAvgRating": 1571.3
      }
    ],
    "recentMatches": [
      {
        "matchId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
        "tournamentId": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
        "occurredAt": "2026-10-03T19:12:00+00:00",
        "result": "win",
        "opponentTeamId": "2f3e4d5c-6b7a-4899-8a1b-2c3d4e5f6a7b",
        "opponentTeamName": "Nébuleuse"
      }
    ],
    "h2h": [
      {
        "opponentUserId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "opponentDisplayName": "Orbite",
        "opponentBattleTag": "Orbite#1187",
        "wins": 1,
        "losses": 0,
        "games": 1
      }
    ],
    "achievements": {
      "badges": [],
      "palmares": [
        {
          "tournamentId": "6e5d4c3b-2a19-4087-b6e5-d4c3b2a19087",
          "tournamentName": "Coupe de printemps 2026",
          "tournamentSlug": "coupe-printemps-2026",
          "teamId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "teamName": "Les Aurores",
          "rank": 2,
          "date": "2026-04-19"
        }
      ],
      "seasons": []
    }
  }
}
400PublicApiError

Identifiant invalide (UUID exigé) — BAD_REQUEST.

Exemple
{
  "error": "Invalid match id",
  "code": "BAD_REQUEST"
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/teams/{id}Sans auth

Équipe publique + roster public (sans données privées).

Équipe de l'espace servi, trouvée par id puis par slug (404 si aucune). Le roster ne porte que le pseudo, le rôle et le statut de remplaçante : ni email, ni identifiant Discord. Ordre du roster non garanti.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 120 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring

Id (UUID) ou slug de l'équipe.

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Détail équipe.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
    "name": "Les Aurores",
    "short_name": "AUR",
    "slug": "les-aurores",
    "logo_url": "https://example.org/logos/aurores.png",
    "roster": [
      {
        "display_name": "Nova",
        "role": "player",
        "is_substitute": false
      },
      {
        "display_name": "Kestrel",
        "role": "player",
        "is_substitute": true
      }
    ]
  }
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/tournaments/{id}/arbitrationSans auth

Métriques d'arbitrage agrégées (non-nominatives) du tournoi.

Agrégat de l'activité d'arbitrage (litiges) d'un tournoi public : volume, résolution, respect du SLA, répartition des litiges ouverts. AUCUN identifiant ni raison — que des nombres. 404 si le tournoi n'est pas public (draft / archived / inconnu).

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring

Id (UUID) ou slug du tournoi.

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Métriques d'arbitrage agrégées du tournoi.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "tournamentId": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
    "tournamentName": "Coupe d'automne 2026",
    "tournamentSlug": "coupe-automne-2026",
    "metrics": {
      "totalDisputes": 4,
      "open": 1,
      "resolved": 3,
      "avgResolutionMinutes": 43,
      "medianResolutionMinutes": 38,
      "withinSlaCount": 2,
      "slaComplianceRate": 0.6667,
      "openBreakdown": {
        "breached": 0,
        "approaching": 1,
        "fresh": 0
      },
      "slaMinutes": 60
    }
  }
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/tournaments/{id}Sans auth

Détail d'un tournoi public + résumé des stages.

Tournoi de l'espace servi, trouvé par id ou slug, s'il est published, running ou completed ; sinon (inconnu, draft, archived) 404. Les phases sont triées par ordre d'affichage ; leur status vaut active ou inactive.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring

Id (UUID) ou slug du tournoi.

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Détail tournoi.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "id": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
    "name": "Coupe d'automne 2026",
    "slug": "coupe-automne-2026",
    "game": "overwatch",
    "status": "running",
    "start_date": "2026-10-03",
    "end_date": "2026-10-25",
    "format": null,
    "stages": [
      {
        "id": "7a1d2e3f-4b5c-4d6e-8f70-1a2b3c4d5e6f",
        "name": "Phase de groupes",
        "stage_type": "group",
        "status": "inactive"
      },
      {
        "id": "9b8c7d6e-5f4a-4b3c-a2d1-0e9f8a7b6c5d",
        "name": "Bracket final",
        "stage_type": "bracket",
        "status": "active"
      }
    ]
  }
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/tournaments/{id}/matchesSans auth

Matches d'un tournoi (overlays de bracket).

Matchs d'un tournoi public (404 si le tournoi est inconnu ou non public), triés par scheduled_at croissante. Pas de pagination : tous les matchs retenus sont renvoyés.

Statuts renvoyés : pending, ongoing, finished UNIQUEMENT. Un match walkover (victoire par forfait, qui fait pourtant avancer le bracket), disputed, postponed ou cancelled n'apparaît PAS dans la liste.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 30 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring

Id (UUID) ou slug du tournoi.

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

stageIdrequêtestring

Égalité stricte sur la phase ; non validé (une valeur inconnue renvoie une liste vide).

statusrequêteenum(pending | ongoing | finished)

Une valeur hors de l'énumération est ignorée (aucun 400) : la route renvoie alors les trois statuts.

Réponses

StatutSchémaDescription
200object

Liste des matches publics du tournoi.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": [
    {
      "id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
      "stage_id": "9b8c7d6e-5f4a-4b3c-a2d1-0e9f8a7b6c5d",
      "round_number": 1,
      "bracket_side": "wb",
      "team1_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "team1_name": "Les Aurores",
      "team1_logo_url": "https://example.org/logos/aurores.png",
      "team2_id": "2f3e4d5c-6b7a-4899-8a1b-2c3d4e5f6a7b",
      "team2_name": "Nébuleuse",
      "team2_logo_url": null,
      "team1_score": 2,
      "team2_score": 1,
      "winner_team_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "status": "finished",
      "scheduled_at": "2026-10-03T18:00:00+00:00"
    }
  ]
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/tournaments/{id}/standingsSans auth

Classement final du tournoi (vide si non finalisé).

Classement final d'un tournoi public (404 si le tournoi est inconnu ou non public). Liste VIDE tant que le tournoi n'a pas été finalisé : ce n'est pas une erreur.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
idrequischeminstring

Id (UUID) ou slug du tournoi.

tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

Réponses

StatutSchémaDescription
200object

Classement final trié par rank.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": [
    {
      "rank": 1,
      "teamId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "teamName": "Les Aurores",
      "teamSlug": "les-aurores",
      "logoUrl": "https://example.org/logos/aurores.png",
      "prize": null
    }
  ]
}
404PublicApiError

Ressource inconnue dans l'espace servi, ou non publique — NOT_FOUND.

Exemple
{
  "error": "Tournament not found",
  "code": "NOT_FOUND"
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/v1/tournamentsSans auth

Liste des tournois publics (published/running/completed).

Tournois de l'espace servi dont le statut est published, running ou completed (jamais draft ni archived), triés par start_date décroissante puis created_at décroissante. pagination.count est le total avant pagination.

Espace servi : voir « Quel espace répond » dans l'introduction. Le jeton Authorization est ignoré ici.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP pour cet endpoint. Cache : 60 s.

Paramètres

ParamètreEmplacementTypeDescription
tenantrequêtestring

Slug de l'espace à lire (ex. pogtv). Absent, inconnu ou espace désactivé : sur owwomenscup.fr, c'est l'espace Women's Cup qui répond, SANS erreur. Le jeton Authorization ne le remplace pas : ces lectures l'ignorent. Appelée sur le domaine propre d'un espace, l'API sert cet espace (le domaine prime sur ce paramètre).

statusrequêteenum(published | running | completed)

Filtre sur un statut public. Une valeur hors de l'énumération est ignorée (aucun 400) : la route renvoie alors les trois statuts.

gamerequêtestring

Égalité stricte sur le jeu.

limitrequêteinteger

Ramené dans [1, 100] ; une valeur non numérique vaut 50.

offsetrequêteinteger

Ramené à 0 au minimum.

Réponses

StatutSchémaDescription
200object

Page de tournois publics + pagination.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": [
    {
      "id": "3f2c8a1e-6b4d-4c7a-9e21-5d8f0b7a1c34",
      "name": "Coupe d'automne 2026",
      "slug": "coupe-automne-2026",
      "game": "overwatch",
      "status": "running",
      "start_date": "2026-10-03",
      "end_date": "2026-10-25",
      "format": null
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "count": 1
  }
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}
get/api/public/webhook-eventsSans auth

Catalogue public des types d'événements webhook souscriptibles.

Catalogue anonyme des types d'événements qu'un espace peut recevoir en webhook sortant, et convention de signature. Dérivé de la liste blanche WEBHOOK_EVENT_TYPES : les événements Discord internes n'y figurent jamais, même avec un abonnement *.

ATTENTION : match.forfeit figure au catalogue mais n'est émis par aucun code aujourd'hui — un abonnement à cet événement ne reçoit rien.

LIVRAISON (non exposée par cette route, décrite ici pour l'intégration) :

  • un POST par événement et par abonnement actif de l'espace ; corps = { id, event, tenantId, timestamp, data } ; la forme de data dépend de l'événement et n'est pas décrite dans cette spec ;
  • en-têtes : Content-Type: application/json, User-Agent: conference-website-webhooks/1, X-Webhook-Event, X-Webhook-Id, X-Tenant-Id et X-Webhook-Signature: sha256=<hex> (HMAC-SHA256 du corps BRUT avec le secret de l'abonnement, affiché une seule fois à sa création) ;
  • une livraison réussit sur toute réponse 2xx reçue en moins de 8 s ;
  • le répartiteur passe chaque minute : un échec est retenté au passage suivant, 5 tentatives au plus, et seuls les événements des dernières 24 h sont examinés ;
  • 15 échecs consécutifs sur un abonnement le désactivent ; un succès remet le compteur à zéro.

Abonnements : back-office /admin/webhooks.

Anonyme, CORS *. Limite : 120 requêtes par minute par IP. Cache : 3600 s.

Réponses

StatutSchémaDescription
200object

Catalogue d'événements + convention de signature.

En-têtes

  • Cache-Control

    Posé par la route sur toute réponse 200 : public, s-maxage=<N>, stale-while-revalidate=<N/2>. La valeur de N dépend de l'endpoint (indiquée dans sa description). Le CDN met en cache par URL complète, paramètres de query compris : une donnée modifiée peut mettre jusqu'à N secondes à apparaître sur une URL déjà servie.

Exemple
{
  "data": {
    "events": [
      {
        "type": "match.finished",
        "description": "A match has finished and a result has been recorded."
      }
    ],
    "signature": {
      "header": "X-Webhook-Signature",
      "algo": "HMAC-SHA256",
      "format": "sha256=<hex>"
    }
  }
}
405PublicApiError

Méthode autre que GET / OPTIONS — METHOD_NOT_ALLOWED.

En-têtes

  • Allow

    Méthodes acceptées par la route.

Exemple
{
  "error": "Method not allowed",
  "code": "METHOD_NOT_ALLOWED"
}
429PublicApiError

Limite de débit par IP dépassée. Deux limiteurs se suivent, avec la même limite (120 requêtes par minute, compteur distinct par endpoint) : un limiteur en mémoire par instance, dont le corps n'a PAS de code, puis un compteur durable partagé (RATE_LIMITED, laisse passer si le compteur est indisponible). Respecter Retry-After.

En-têtes

  • Retry-After

    Secondes à attendre avant de réessayer.

Exemple — Limiteur en mémoire (sans code)
{
  "error": "Trop de requêtes. Réessayez plus tard."
}
Exemple — Compteur durable
{
  "error": "Trop de requêtes. Réessayez plus tard.",
  "code": "RATE_LIMITED"
}
500PublicApiError

Erreur serveur — INTERNAL. Réessayer plus tard.

Exemple
{
  "error": "Internal server error",
  "code": "INTERNAL"
}

Schémas

Error

PropriétéTypeDescription
errorrequisstringMessage lisible côté client.
codestringCode machine, présent seulement quand la route en pose un. Catalogues : `PublicApiErrorCode` (surface publique, spec publique) et la section « Catalogue des codes d'erreur » de `docs/BOT_API_CONTRACT.md` (bot).
fieldsobjectErreurs de validation par champ (`INVALID_BODY` / `INVALID_QUERY` des middlewares bot et public).
detailsobjectDétail libre, posé par quelques routes admin seulement.

Uuid

string <uuid>

PublicScrimRequestInput

PropriétéTypeDescription
targetTeamIdstring <uuid>
targetTeamSlugstring
fromTeamNamestring
requesterNamerequisstring
requesterEmailstring <email>
requesterDiscordstring
preferredDatestring <date-time>
formatstring
messagerequisstring
honeypotstringDoit etre vide (anti-bot).
captchaTokenstring
captchaAnswerstring

PublicV1Pagination

PropriétéTypeDescription
limitrequisinteger
offsetrequisinteger
countrequisintegerNombre total d'éléments avant pagination (tournois) ou nombre d'éléments retournés dans la page (leaderboard).

PublicV1TournamentSummary

PropriétéTypeDescription
idrequisstring <uuid>
namerequisstring
slugrequisstring | null
gamerequisstring | null
statusrequisstring
start_daterequisstring | null
end_daterequisstring | null
formatrequisstring | null

PublicV1StageSummary

PropriétéTypeDescription
idrequisstring <uuid>
namerequisstring | null
stage_typerequisstring | null
statusrequisstringDérivé de `is_active` (active | inactive).

PublicV1TournamentDetail

PropriétéTypeDescription
idrequisstring <uuid>
namerequisstring
slugrequisstring | null
gamerequisstring | null
statusrequisstring
start_daterequisstring | null
end_daterequisstring | null
formatrequisstring | null
stagesrequisarray<PublicV1StageSummary>

PublicV1Match

PropriétéTypeDescription
idrequisstring <uuid>
stage_idrequisstring | null
round_numberrequisinteger | null
bracket_siderequisstring | null
team1_idrequisstring | null
team1_namerequisstring | null
team1_logo_urlrequisstring | nullLogo public de l’équipe (peut être null).
team2_idrequisstring | null
team2_namerequisstring | null
team2_logo_urlrequisstring | nullLogo public de l’équipe (peut être null).
team1_scorerequisinteger | null
team2_scorerequisinteger | null
winner_team_idrequisstring | null
statusrequisstring
scheduled_atrequisstring | null

PublicV1MatchGame

PropriétéTypeDescription
map_namerequisstring | null
map_orderrequisinteger | null
team1_scorerequisinteger | null
team2_scorerequisinteger | null
winner_team_idrequisstring | null

PublicV1MatchDetail

PropriétéTypeDescription
idrequisstring <uuid>
stage_idrequisstring | null
round_numberrequisinteger | null
bracket_siderequisstring | null
team1_idrequisstring | null
team1_namerequisstring | null
team1_logo_urlrequisstring | nullLogo public de l’équipe (peut être null).
team2_idrequisstring | null
team2_namerequisstring | null
team2_logo_urlrequisstring | nullLogo public de l’équipe (peut être null).
team1_scorerequisinteger | null
team2_scorerequisinteger | null
winner_team_idrequisstring | null
statusrequisstring
scheduled_atrequisstring | null
gamesrequisarray<PublicV1MatchGame>

PublicV1Standing

PropriétéTypeDescription
rankrequisinteger
teamIdrequisstring <uuid>
teamNamerequisstring | null
teamSlugrequisstring | null
logoUrlrequisstring | null
prizerequisstring | null

PublicV1ArbitrationMetrics

Métriques d'arbitrage AGRÉGÉES et NON NOMINATIVES d'un tournoi. Aucun identifiant (équipe / match / joueuse), aucune raison de litige : que des compteurs, durées et taux.

PropriétéTypeDescription
totalDisputesrequisintegerMatchs ayant eu un litige (opened_at renseigné OU status=disputed OU résolus).
openrequisintegerLitiges non résolus (status=disputed).
resolvedrequisintegerLitiges avec un horodatage de résolution.
avgResolutionMinutesrequisnumber | nullTemps moyen de résolution (minutes) sur les résolus ; null si aucun.
medianResolutionMinutesrequisnumber | nullTemps médian de résolution (minutes) sur les résolus ; null si aucun.
withinSlaCountrequisintegerLitiges résolus dans le SLA (durée <= slaMinutes).
slaComplianceRaterequisnumber | nullwithinSlaCount / resolved (0..1) ; null si resolved=0.
openBreakdownrequisobjectRépartition SLA des litiges OUVERTS.
slaMinutesrequisintegerFenêtre SLA (minutes) du tenant utilisée pour le calcul.

PublicV1TournamentArbitration

PropriétéTypeDescription
tournamentIdrequisstring <uuid>
tournamentNamerequisstring
tournamentSlugrequisstring | null
metricsrequisPublicV1ArbitrationMetrics

PublicV1TeamMember

PropriétéTypeDescription
display_namerequisstring | null
rolerequisstring | null
is_substituterequisboolean

PublicV1Team

PropriétéTypeDescription
idrequisstring <uuid>
namerequisstring
short_namerequisstring | null
slugrequisstring | null
logo_urlrequisstring | null
rosterrequisarray<PublicV1TeamMember>

PublicApiErrorCode

Catalogue des valeurs de code que la surface publique (/api/public/*) peut renvoyer. C'est la valeur à tester côté client : error est un texte lisible qui peut changer.

Deux réponses d'erreur n'ont PAS de code : le 429 du limiteur par IP ({ "error": "Trop de requêtes. Réessayez plus tard." }) et le 403 de plan (PublicPlanDenial, où plan_required est la valeur de error).

Tenu à jour par tests/unit/apiErrorCodeCatalog.test.ts : un code émis par le code et absent d'ici (ou l'inverse) fait échouer la suite.

ValeurDescription
BAD_REQUEST

400 — requête invalide détectée par la route (identifiant non-UUID là où un UUID est exigé, match bye ou sans ses deux équipes pour une écriture de score).

INVALID_BODY

400 — corps JSON refusé par la validation (écriture authentifiée). Le détail par champ est dans fields.

INVALID_QUERY

400 — paramètres d'URL refusés par la validation (écriture authentifiée ; ex. id de match non-UUID). Le détail par champ est dans fields.

UNAUTHORIZED

401 — jeton absent, mal formé (préfixe pk_live_ requis), inconnu, révoqué OU expiré. Ces cas ne sont pas distingués.

INSUFFICIENT_SCOPE

403 — jeton valide, mais la portée exigée par la route manque (aucune implication entre portées). Vérifié APRÈS le plan.

NOT_FOUND

404 — ressource inconnue dans l'espace servi, ou non publique (tournoi en brouillon ou archivé, match dans un statut non public, ligue en brouillon ou privée). Sur les formulaires de retrait, même réponse qu'un lien invalide.

METHOD_NOT_ALLOWED

405 — méthode non acceptée ; l'en-tête Allow liste les méthodes valides.

CONFLICT

409 — conflit d'état (ex. match déjà finished, walkover ou cancelled).

RATE_LIMITED

429 — limite de débit dépassée. Sur les lectures, limiteur durable par IP et par endpoint ; sur les écritures authentifiées, débit par minute du plan (X-RateLimit-Scope: minute). Respecter Retry-After.

ACTOR_RATE_LIMIT

429 — limite par jeton dépassée (écriture authentifiée), en plus de la limite par IP. Respecter Retry-After.

QUOTA_EXCEEDED

429 — quota mensuel du plan épuisé (X-RateLimit-Scope: month, Retry-After jusqu'au mois suivant, UTC). Jamais pour une clé partenaire.

MAINTENANCE_MODE

503 — écritures gelées pendant une maintenance (Retry-After: 60). Les lectures restent servies.

INTERNAL

500 — erreur serveur. Aussi 503 Service unavailable. quand la base est indisponible (écriture authentifiée). Réessayer plus tard.

CAPTCHA

400 — formulaires du site uniquement (free-players, team-openings) — captcha refusé. Hors intégration partenaire.

VALIDATION

400 — formulaires du site uniquement (free-players, team-openings) — champ invalide ou adresse email refusée. Hors intégration partenaire.

INVALID_TOKEN

400 — formulaires de retrait uniquement (free-players/remove, team-openings/remove) — lien de retrait invalide. Hors intégration partenaire.

PublicApiError

Corps d'erreur de la surface publique. code est absent sur le 429 du limiteur par IP ; fields n'existe que pour INVALID_BODY / INVALID_QUERY.

PropriétéTypeDescription
errorrequisstringMessage lisible. Peut changer sans préavis — ne pas le comparer.
codePublicApiErrorCode
fieldsobjectErreurs par champ (`INVALID_BODY` / `INVALID_QUERY` uniquement).

PublicPlanDenial

403 renvoyé quand le plan de l'espace propriétaire du jeton n'ouvre pas l'accès demandé. ATTENTION : plan_required est la valeur de error, il n'y a PAS de champ code. Une clé partenaire (comp) n'obtient jamais cette réponse. Évalué AVANT la portée du jeton.

PropriétéTypeDescription
errorrequisstring
messagerequisstringExplication lisible (plan à souscrire).
requiredCapabilityrequisenum(apiRead | apiWrite)Capacité manquante. Seule l'unique route d'écriture passe par ce contrôle aujourd'hui : en pratique `apiWrite`.

PublicNewsletterSubscribeInput

PropriétéTypeDescription
emailrequisstring <email>
sourcestringProvenance libre (ex. `footer`, `landing`). Défaut `public`.
honeypotstringDoit être vide (anti-bot).
captchaTokenstring
captchaAnswerstring

LeaderboardPlayer

PropriétéTypeDescription
teamNamerequisstring | nullNom de l'équipe la plus récente de la joueuse. Sert de contexte d'affichage, pas de filtre.
teamSlugrequisstring | null
teamLogoUrlrequisstring | nullLogo de l'équipe, utilisé comme repli d'avatar quand `avatarUrl` est nul.
userIdrequisstring
displayNamerequisstring | null
battleTagrequisstring | null
avatarUrlrequisstring | null
ratingrequisnumber
rdrequisnumber
gamesPlayedrequisinteger
winsrequisinteger
lossesrequisinteger
rankrequisinteger

PublicFreePlayerSignupInput

PropriétéTypeDescription
displayNamerequisstring
emailrequisstring <email>Contact privé. N'est JAMAIS renvoyé par le GET de cette route ; seules les capitaines authentifiées y accèdent via /api/teams/free-players.
rolesrequisarray<enum(tank | dps | support | flex)>
levelenum(unknown | bronze | silver | gold | platinum | emerald | diamond | master | grandmaster | champion)Défaut `unknown` — il n'y a aucun rang minimum pour participer.
availabilitystring
notestring
contactDiscordstring
shareAcrossTenantsbooleanRendre l'annonce lisible depuis les autres espaces volontaires. Défaut : false.
honeypotstringDoit être vide (anti-bot).
captchaTokenstring
captchaAnswerstring

PublicFreePlayer

Projection ANONYMISEE : aucun moyen de contact (ni email, ni pseudo Discord, ni snowflake).

PropriétéTypeDescription
idrequisUuid
namerequisstring
rolesrequisarray<enum(tank | dps | support | flex)>
levelstring | null
availabilitystring | null
notestring | null
sincestring <date-time> | null

PublicTeamOpeningInput

contactEmail est obligatoire — email est accepté comme alias (c'est le nom du champ côté fiches joueuses, et un client écrit en le recopiant ne doit pas être refusé en silence). Fournir l'un des deux.

PropriétéTypeDescription
teamNamerequisstring
contactEmailstring <email>Contact privé. N'est JAMAIS renvoyé par le GET de cette route ; seules les personnes connectées y accèdent via /api/team-openings/contact.
emailstring <email>Alias historique de `contactEmail` (celui de /api/public/free-players).
rolesrequisarray<enum(tank | dps | support | flex)>Postes RECHERCHÉS par l'équipe (sens inverse d'une fiche joueuse).
levelenum(unknown | bronze | silver | gold | platinum | emerald | diamond | master | grandmaster | champion)Niveau de l'ÉQUIPE. Défaut `unknown` — aucun rang minimum requis.
availabilitystring
notestring
contactDiscordstring
honeypotstringDoit être vide (anti-bot).
captchaTokenstring
captchaAnswerstring

PublicTeamOpening

Projection ANONYMISEE d'une annonce de recrutement : aucun moyen de contact (ni email, ni pseudo Discord).

PropriétéTypeDescription
idrequisUuid
teamNamerequisstring
rolesrequisarray<enum(tank | dps | support | flex)>Postes recherches.
levelstring | null
availabilitystring | null
notestring | null
sincestring <date-time> | null

PublicLeague

PropriétéTypeDescription
idrequisstring <uuid>
namerequisstring
slugrequisstring
descriptionrequisstring | null
gamerequisstring | null
statusrequisenum(draft | active | finished | archived)
start_daterequisstring | null
end_daterequisstring | null
points_tablerequisobject
is_publicrequisboolean
created_atrequisstring
updated_atrequisstring

LeagueStandingPublic

PropriétéTypeDescription
teamIdrequisstring
teamNamerequisstring | null
teamSlugrequisstring | null
logoUrlrequisstring | null
pointsrequisnumber
tournamentsCountedrequisinteger
scrimsCountedrequisintegerScrims de la saison joués par l'équipe. Un scrim n'a pas de rang final : il compte à part, et n'entre ni dans `tournamentsCounted` ni dans `bestRank`.
bestRankrequisinteger | null
rankrequisinteger

LeagueTournamentRef

PropriétéTypeDescription
idrequisstring
namerequisstring | null
slugrequisstring | null
weightrequisnumber

LeagueScrimRef

PropriétéTypeDescription
idrequisstring
namerequisstring | null
slugrequisstring | null
weightrequisnumber
team1Namerequisstring | null
team2Namerequisstring | null
team1Scorerequisinteger | null
team2Scorerequisinteger | null
scheduledDaterequisstring | null

PlayerProfileCore

PropriétéTypeDescription
userIdrequisstring
displayNamerequisstring | null
battleTagrequisstring | null
avatarUrlrequisstring | null
twitchrequisstring | nullChaîne Twitch déclarée (handle nu ou URL), ou à défaut celle de sa fiche de roster ; null si non déclarée.
unratedrequisbooleantrue quand la joueuse n'a encore joué aucun match classé : les chiffres de rating valent alors 0 et ne doivent PAS être affichés.
ratingrequisnumber
rdrequisnumber
volatilityrequisnumber
peakRatingrequisnumber
gamesPlayedrequisinteger
winsrequisinteger
lossesrequisinteger
rankrequisinteger | nullnull pour une joueuse non classée.

PlayerProfileHistoryPoint

PropriétéTypeDescription
matchIdrequisstring
tournamentIdrequisstring | null
occurredAtrequisstring
ratingBeforerequisnumber
ratingAfterrequisnumber
resultrequisenum(win | loss | draw)
opponentAvgRatingrequisnumber | null

PlayerProfileRecentMatch

PropriétéTypeDescription
matchIdrequisstring
tournamentIdrequisstring | null
occurredAtrequisstring
resultrequisenum(win | loss | draw)
opponentTeamIdrequisstring | null
opponentTeamNamerequisstring | null

PlayerProfileH2H

PropriétéTypeDescription
opponentUserIdrequisstring
opponentDisplayNamerequisstring | null
opponentBattleTagrequisstring | null
winsrequisinteger
lossesrequisinteger
gamesrequisinteger

ProfileAchievements

Badges, palmarès (placements en tournoi) et historique de saison dérivés du parcours de la joueuse (utils/profile/achievements.ts). Toujours présent, vide si rien à agréger.

PropriétéTypeDescription
badgesrequisarray<ProfileBadge>
palmaresrequisarray<ProfilePlacement>
seasonsrequisarray<ProfileSeason>

ProfileBadge

PropriétéTypeDescription
keyrequisstring
labelrequisstring
descriptionrequisstring
tierrequisenum(bronze | silver | gold | platinum) | null

ProfilePlacement

PropriétéTypeDescription
tournamentIdrequisstring
tournamentNamerequisstring | null
tournamentSlugrequisstring | null
teamIdrequisstring
teamNamerequisstring | null
rankrequisinteger
daterequisstring | null

ProfileSeason

PropriétéTypeDescription
leagueIdrequisstring
leagueNamerequisstring | null
leagueSlugrequisstring | null
teamIdrequisstring
teamNamerequisstring | null
rankrequisinteger | null
pointsrequisnumber

Cette page est générée depuis la spec OpenAPI du dépôt à chaque déploiement. Des vérifications automatiques confrontent au code les endpoints, les méthodes, les réponses de succès, les exemples et les codes d'erreur ; le reste est rédigé à la main.