Appearance
Conventions API
Ce qui est commun à toutes les queries et mutations de l'API — pagination, format des montants, format des erreurs, limites de fréquence, durcissement des requêtes. À lire une fois pour éviter les pièges les plus fréquents (troncature des messages d'erreur en production, sérialisation des montants) plutôt que de les découvrir en intégration.
Pagination — skip / take
Il n'y a pas de pagination par curseur sur cette API : pas de champ PageInfo, pas de endCursor/hasNextPage façon Relay. Chaque query de liste expose ses propres arguments skip (offset) et take (taille de page). skip est un entier positif ou nul, sans borne haute, avec une valeur par défaut de 0. take est borné par un minimum (1) et par un maximum propre à chaque query, avec une valeur par défaut également propre à chaque query (souvent 30).
Points à retenir :
skipdémarre à0,takea une valeur par défaut propre à chaque query (souvent30).- Le plafond de
taken'est pas uniforme sur toute l'API — chaque query déclare son propre maximum (valeurs observées :50,100,200selon la query). Vérifiez le plafond réel de la query que vous consommez plutôt que de supposer une constante globale — une valeur detakeau-delà du plafond déclaré est rejetée à la validation des arguments (erreur avant exécution, pas de troncature silencieuse). - Il n'existe pas de convention de tri implicite garantie sur toutes les listes : si l'ordre vous importe, vérifiez si la query expose un argument de tri dédié plutôt que de compter sur un ordre stable par défaut.
Montants — BigInt en centimes
Tous les champs monétaires (prix, chiffre d'affaires, montants de remboursement…) sont exprimés en centimes et exposés via le scalaire BigInt. Exemples : Event.memberPrice / Event.nonMemberPrice, Mission.price, Payment.amount, Company.turnover / Company.shareCapital, EventOrder.subtotal / serviceFee / total.
Piège de sérialisation : le scalaire BigInt sérialise la valeur sur le fil comme une chaîne de caractères (pas un Number JSON), précisément pour éviter la perte de précision d'un entier JS au-delà de 2^53. Si votre client désérialise ces champs en Number sans précaution, vous risquez soit un montant tronqué sur de grandes valeurs, soit un type inattendu (string au lieu de number) selon votre client GraphQL. Traitez ces champs comme des BigInt/chaînes numériques, convertissez explicitement, ne faites pas d'arithmétique flottante dessus. Pour convertir en unité affichable, divisez par 100 seulement après avoir converti en type numérique adapté à votre langage cible (BigInt, Decimal…), jamais en Number intermédiaire sur de grands montants.
Exception à connaître : Mission.depositPercentage est un Float classique (pourcentage, pas un montant en centimes), valant 30 par défaut. Ne lui appliquez pas la même logique de conversion qu'aux champs BigInt.
Format des erreurs
En staging et en production, le formatage des erreurs GraphQL est volontairement restreint :
- Seul
extensions.codeest conservé dansextensions(tout le reste — stacktrace, erreur d'origine, détails internes — est retiré). - Le message est tronqué à 500 caractères, avec un suffixe
... [truncated]si dépassement.
Conséquence pratique : ne construisez jamais de logique d'intégration sur le contenu du message d'erreur en production — basez-vous sur extensions.code. Le message reste utile pour l'affichage humain (log, support) mais peut être tronqué et son contenu exact n'est pas contractuel.
Catalogue des codes d'erreur
Table établie à la main à partir des codes explicitement posés par l'API — elle n'est pas exhaustive de toutes les erreurs possibles, mais couvre les cas qu'un intégrateur rencontre le plus souvent.
extensions.code | Signification | Où |
|---|---|---|
ACCOUNT_LOCKED | Compte verrouillé après trop de tentatives de connexion échouées | login |
TICKETS_NOT_OPEN | Billetterie pas encore ouverte pour le statut (membre/non-membre) de l'acheteur — extensions.params.opensAt renseigné | registerToEvent et createEventOrder |
MEMBER_ONLY | Événement réservé aux adhérents du business club, compte non-adhérent | registerToEvent et createEventOrder |
EVENT_PAST | L'événement est déjà passé | registerToEvent et createEventOrder |
EVENT_FULL | Capacité de l'événement atteinte | registerToEvent et createEventOrder |
QUOTA_EXHAUSTED | Quota d'événements gratuits de l'entreprise épuisé — non atteignable en l'état : le quota d'abonnement n'est pas appliqué, voir Events & Pricing | registerToEvent et createEventOrder |
ALREADY_REGISTERED | L'utilisateur/l'entreprise est déjà inscrit(e) à cet événement | registerToEvent et createEventOrder |
ALREADY_REGISTERED_OTHER_COMPANY | Déjà inscrit à cet événement via une autre entreprise | registerToEvent |
NON_MEMBER_QUOTA_EXHAUSTED | Quota de places non-adhérents épuisé pour cet événement (BusinessClub.nonMemberEventQuota) | registerToEvent et createEventOrder |
TOO_MANY_REQUESTS | Limite de fréquence dépassée (voir section suivante) | toute opération soumise à throttling — accompagné de extensions.http.status = 429, extensions.limit, extensions.retryAfter |
GRAPHQL_VALIDATION_FAILED | Requête rejetée en validation statique avant exécution (profondeur, nombre de tokens/alias — voir « Durcissement ») | n'importe quelle query/mutation |
Ces codes d'accès (MEMBER_ONLY, TICKETS_NOT_OPEN, NON_MEMBER_QUOTA_EXHAUSTED, EVENT_PAST, EVENT_FULL, ALREADY_REGISTERED) s'appliquent aux deux mutations d'inscription, avec la même sémantique : traitez-les indifféremment sur registerToEvent et sur createEventOrder. Détail dans Events & Pricing.
Cas sans code applicatif dédié : les échecs d'authentification (JWT absent/invalide/expiré) et d'autorisation (accès refusé à une ressource) lèvent des exceptions HTTP standards (UnauthorizedException, ForbiddenException) sans poser de extensions.code explicite. N'inférez pas de valeur fixe de code pour ces cas — repérez-les plutôt par un message contenant « Unauthorized »/« not authorized », ou traitez-les comme un échec générique nécessitant une reconnexion (401) ou un accès non autorisé (403) selon le contexte de l'appel.
HTTP 200 par défaut : comme la quasi-totalité des serveurs GraphQL, cette API répond HTTP 200 même en cas d'erreur applicative — le statut d'erreur vit dans le tableau errors de la réponse GraphQL, pas dans le code de statut HTTP. Seules certaines erreurs posent explicitement un statut HTTP différent via extensions.http.status (c'est le cas du throttling, 429 — voir ci-dessus). Ne testez donc pas le code HTTP pour détecter une erreur métier : inspectez toujours le tableau errors de la réponse.
Limites de fréquence (rate limiting)
Deux niveaux de limitation coexistent, tous deux comptabilisés côté serveur (pas d'en-tête X-RateLimit-* exposé aujourd'hui) :
- Global, par IP : plafond de requêtes par minute sur l'ensemble de l'API.
login, spécifique :5tentatives /15minutes par IP et5tentatives /15minutes par email — les deux limites s'appliquent indépendamment, la première atteinte bloque l'appel.
Un dépassement lève une erreur avec extensions.code: "TOO_MANY_REQUESTS" (voir table ci-dessus), accompagnée de extensions.retryAfter (secondes avant réessai) et d'un statut HTTP 429. Prévoyez un backoff côté intégration plutôt que de rejouer immédiatement un appel en échec — en particulier pour login, où la limite par email peut être atteinte même si vos appels proviennent de plusieurs IP différentes.
Durcissement des requêtes
Au-delà de la validation de schéma standard, les requêtes GraphQL passent une couche de règles de validation statique supplémentaires avant exécution :
- Profondeur maximale — une query imbriquant des champs au-delà d'un certain nombre de niveaux est rejetée avant exécution.
- Nombre de tokens maximal — protège contre les requêtes anormalement volumineuses.
- Nombre d'alias maximal — protège contre l'amplification par alias (répéter le même champ sous des dizaines d'alias distincts dans une seule requête).
Une requête qui dépasse l'une de ces limites échoue avec extensions.code: "GRAPHQL_VALIDATION_FAILED" (voir table ci-dessus) — l'échec a lieu en validation, aucun resolver n'est exécuté. Si vous générez des requêtes dynamiquement (agrégation de plusieurs entités dans un seul appel via des alias, par exemple), gardez des requêtes de taille et de profondeur raisonnables, ou fractionnez-les en plusieurs appels.