Authentification
Toutes les requêtes doivent inclure la clé API dans le header x-api-key. La clé est fournie par PTM lors de l'onboarding partenaire.
Ne jamais exposer la clé API côté client. Tous les appels doivent transiter par votre backend.
Codes d'erreur
| Code | Signification | Description |
|---|---|---|
| 200 | OK | Succès, y compris les cas métier (not_found, multiple…) |
| 201 | Created | Inscription créée (statut confirmed, pending ou waitlist) |
| 400 | Bad Request | Paramètre requis manquant ou mal formé (email, téléphone) |
| 401 | Unauthorized | Clé API absente, invalide ou désactivée |
| 404 | Not Found | Ressource inexistante ou non publiée sur Pista |
| 409 | Conflict | Doublon d'inscription (paire déjà inscrite) |
| 500 | Server Error | Erreur interne PTM |
Liste des tournois
Retourne la liste des tournois publiés sur Pista avec les inscriptions ouvertes (registration_open = true). Tous les filtres sont optionnels et cumulables.
| Paramètre | Type | Description |
|---|---|---|
| date_from | date (YYYY-MM-DD) | Tournois à partir de cette date |
| date_to | date (YYYY-MM-DD) | Tournois jusqu'à cette date |
| gender | string | Filtrer par genre : Masculin, Féminin, Mixte |
| level | string | Filtrer par niveau : P25, P100, P250… |
| club_id | uuid | Restreindre aux tournois d'un club spécifique |
| lat | number | Latitude du point de référence (requis avec lng et radius_km) |
| lng | number | Longitude du point de référence |
| radius_km | number | Rayon de recherche en km autour du point lat/lng |
available_spots peut être null si le tournoi n'a pas de limite de paires définie. La réponse est limitée à 100 résultats. Une pagination cursor-based (?cursor=) sera disponible prochainement.
end_date (AAAA-MM-JJ) n'est renseignée que pour un tournoi sur plusieurs jours ; elle vaut null pour un tournoi d'une journée. start_time (HH:MM) peut être null.
status, featured et dotation sont destinés aux sites vitrine de club (clé bornée à un compte) : badge « en cours » / « terminé », mise en avant, prize money. status est l'enum partenaire stable, découplé de notre interne : scheduled, in_progress, completed, cancelled — les mêmes valeurs que le webhook TournamentUpdated. Un agrégateur peut les ignorer sans conséquence.
Détail d'un tournoi
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| id | uuid | requis | Identifiant du tournoi |
Inscription d'une paire
À l'inscription, PTM vérifie chaque joueur auprès de la FFT. Trois issues possibles :
status: confirmed— joueurs vérifiés et places disponibles : la paire est ajoutée au tableau.status: waitlist— tournoi complet : la paire est placée en liste d'attente (avec sa position).status: pending— un joueur n'a pas pu être vérifié (introuvable FFT, licence incorrecte, homonymie, ou service FFT temporairement indisponible) : la place est réservée mais l'inscription attend une validation manuelle du juge-arbitre.
Les champs requis manquants ou mal formés (email, téléphone) sont rejetés en 400 avant toute création.
| Champ | Type | Description |
|---|---|---|
| tournament_id | uuid | requis ID du tournoi cible |
| player1.first_name | string | requis Prénom joueur 1 |
| player1.last_name | string | requis Nom joueur 1 |
| player1.email | string | requis Email joueur 1 (format validé) — le joueur 1 est le contact de la paire |
| player1.phone | string | requis Téléphone joueur 1 (≥ 6 chiffres) |
| player2.first_name | string | requis Prénom joueur 2 |
| player2.last_name | string | requis Nom joueur 2 |
| playerN.tenup_id | string | Identifiant fédéral (idCrm). Recommandé : c'est l'ancre d'identité la plus fiable |
| playerN.classement | number | Classement padel, transmis avec tenup_id |
| playerN.gender | string | "H" ou "F", transmis avec tenup_id |
| playerN.licence_number | string | Licence FFT. Facultative depuis le 29/08/2026 |
| playerN.no_licence | boolean | Le joueur déclare ne pas encore avoir de licence |
| player2.email | string | Facultatif. Validé s'il est fourni |
| player2.phone | string | Facultatif. Validé s'il est fourni |
Aucun identifiant fédéral n'est obligatoire. Envoyez tenup_id quand vous l'avez : c'est le chemin recommandé, et le seul qui n'appelle pas la FFT — PTM fait alors confiance au classement et au gender que vous transmettez, qui doivent donc provenir d'une recherche fédérale et non d'une saisie libre du visiteur. Sans tenup_id, PTM recherche le joueur par nom et licence. Sans rien du tout, l'inscription est acceptée et signalée comme incomplète au juge-arbitre.
⚠️ N'exigez pas la licence de vos utilisateurs. La recherche fédérale publique ne la retourne pas : elle rend le nom, le classement, le club et l'idCrm, jamais le numéro de licence, qui n'est accessible que par un appel authentifié. Ce n'est pas un cas particulier réservé aux fiches masquées, c'est le cas nominal — un formulaire qui exige la licence est infranchissable pour l'ensemble de vos joueurs, pas pour quelques-uns. Mesuré le 30/08/2026 sur des recherches réelles.
L'idCrm le remplace intégralement comme ancre d'identité, et il a l'avantage de ne dépendre d'aucune authentification. C'est la raison pour laquelle la licence est devenue facultative le 29/08/2026.
L'email (format) et le téléphone (au moins 6 chiffres) sont validés ; une valeur mal formée renvoie un 400, y compris pour le joueur 2 dont ces champs sont pourtant facultatifs.
C'est la réponse NOMINALE d'une clé de club. La place n'est pas réservée au POST : elle l'est quand le joueur 1 clique le lien reçu par email. Entre les deux, l'inscription existe mais n'occupe aucune place, et le tournoi peut se remplir. N'annoncez donc pas une place acquise sur cet écran.
expires_at est toujours présent sur un 202 (échéance à 60 minutes). Le jeton de confirmation n'est jamais rendu : il ne transite que par l'email, faute de quoi la preuve ne prouverait plus rien. Rejouer le même POST rend la même demande et n'envoie pas de second email — un bouton « je n'ai pas reçu l'email » serait donc sans effet, mieux vaut inviter à vérifier les indésirables. Seul le joueur 1 est destinataire ; le joueur 2 est prévenu après confirmation.
Le clic aboutit sur une page PTM, qui annonce elle-même le résultat : confirmé, ou liste d'attente avec la position si le tournoi s'est rempli entre-temps. Le message ci-dessus est un repli technique, pas un texte d'interface : rédigez le vôtre.
⚠️ Les réponses 201 ci-dessous ne concernent que les clés agrégateur. Une clé de club reçoit toujours un 202.
players porte le ptm_player_id de chaque slot : stockez-le, c'est la clé de jointure du remplacement de joueur. Le webhook change_type: "roster" renvoie le même identifiant pour un slot inchangé et un identifiant différent pour le slot remplacé — vous diffez dessus. Le champ est absent en liste d'attente (la paire n'existe pas encore ; les identifiants apparaissent à la promotion) et présent en confirmed comme en pending.
field identifie le problème : nom du champ manquant (ex. player2.last_name) ou suffixe _invalid pour un format incorrect (player1.email_invalid, player1.phone_invalid).
Rendu aussi bien pour un tournoi inexistant que pour un tournoi hors du périmètre de votre clé. Les deux cas sont volontairement indistincts : les séparer révélerait l'existence des tournois d'un autre club.
Depuis le 29/08/2026, ces deux cas répondent 409 et non plus 404 : le tournoi existe, seul son état s'oppose à la demande. opens_at n'accompagne que registration_not_yet_open — « pas encore ouvert » et « fermé » appellent des réactions opposées du joueur, revenir ou renoncer.
⚠️ Lisez error avant le code HTTP. Le 409 ne signifie plus « paire déjà inscrite » à lui seul.
Déclenché dès qu'un des deux joueurs est déjà engagé sur ce tournoi, par sa licence ou par son tenup_id. Une demande en attente de confirmation (202) ne bloque personne : elle ne réserve rien.
Désinscription d'une paire
La désinscription n'est possible que pour les inscriptions créées via Pista (source: pista). Elle est bloquée si le tournoi est en cours ou terminé. La promotion automatique de la liste d'attente n'est pas gérée, le JA continue à promouvoir manuellement.
| Champ | Type | Description |
|---|---|---|
| registration_id | uuid | ID de l'inscription à annuler |
Webhooks · disponible (staging)
PTM notifie des changements sur le cycle de vie des inscriptions et des tournois (et non sur les attributs des joueurs : classement/contact ne sont pas synchronisés — servis par GET tournaments/:id). L'émission est active sur staging ; il reste à échanger l'URL d'abonnement et le secret de signature, et à confirmer le mapping de l'annulation (voir plus bas).
Chaque événement est envoyé en POST vers l'URL d'abonnement, signé en HMAC-SHA256 du corps brut (header X-PTM-Signature, hex). Un header X-PTM-Event-Id (UUID) permet la déduplication idempotente. En cas d'échec, réessais en backoff exponentiel (jusqu'à 8 tentatives, puis abandon). Seuls les changements visibles côté partenaire sont émis (pas de notification si le statut projeté ne change pas) ; un changement initié par le partenaire n'est pas réémis vers lui.
cancelled ≠ rejectedUne inscription peut sortir pour deux raisons opposées, et PTM les distingue : rejected = le juge-arbitre refuse une inscription qu'il n'a pas pu vérifier (licence douteuse, joueur injoignable) ; cancelled = désistement ou retrait. Le champ reason accompagne ces deux statuts et donne la cause exacte :
| reason | Signification |
|---|---|
| ja_rejection | Refus du juge-arbitre après vérification impossible (accompagne rejected) |
| ja_cancellation | Le juge-arbitre annule une inscription vérifiée — typiquement un joueur qui se désiste |
| bulk_replace | Annulation en masse : le juge-arbitre a réimporté sa liste de joueurs en mode remplacement. Ce n'est un jugement sur personne — attendez-vous à recevoir l'événement pour toutes les inscriptions du tournoi d'un coup. |
| partner_unregister | Désinscription que vous avez vous-même initiée (ne vous est pas réémise — anti-echo) |
L'annulation d'un tournoi n'émet pas d'événement par inscription : elle arrive via TournamentUpdated avec status: "cancelled", à vous de retirer les inscriptions associées.
change_type distingue deux causes d'émission. "status" : transition du statut projeté confirmed / pending / waitlist / rejected — ex. le juge-arbitre valide une inscription en anomalie (pending → confirmed), promeut une liste d'attente, ou rejette une inscription. "roster" : remplacement d'un joueur dans la paire, à statut inchangé. Le champ roster n'est présent que dans ce second cas, et il est volontairement minimisé — ptm_player_id (identifiant opaque et stable, pour la réconciliation par slot), nom, prénom et licence, sans email, téléphone ni classement ; ces attributs restent servis par GET tournaments/:id. N'est émis que pour les inscriptions d'origine partenaire ; l'ancre de liaison est registration_id, renvoyé par POST tournaments/:id/register.
Émis sur changement du statut projeté du tournoi — scheduled / in_progress / completed / cancelled — et non du statut interne PTM. Les états amont (brouillon, configuration, prêt) sont tous projetés en scheduled : leurs transitions ne génèrent donc aucun événement, ce qui vous épargne le bruit de préparation. → cancelled signifie que toutes les inscriptions du tournoi sont à retirer côté partenaire. N'est émis que pour les tournois ayant au moins une inscription d'origine partenaire.
URL d'abonnement + secret de signature à échanger, sans quoi les événements sont enregistrés mais jamais livrés. Deux points sont désormais tranchés : l'identifiant joueur opaque est ptm_player_id (livré à l'inscription et dans le roster), et l'annulation n'est plus rabattue sur rejected — cancelled est un statut à part entière, accompagné de sa reason.