Conventions générales
- Base URL (dev) :
http://hydrogen.test(Laragon) - Préfixe API : toutes les routes JSON sont sous
/api - Format : JSON:API 1.1
Content-Typeréponse :application/vnd.api+json- Erreurs : tableau
errors[]avecstatus,title,detail, optionnelsource.pointeretmeta
- Authentification : Bearer token via header
Authorization: Bearer <token>Le token est obtenu viaPOST /api/auth/loginet a une durée de vie glissante de 30 jours (renouvelée à chaque requête authentifiée). - Codes d’erreur communs :
400— corps JSON invalide401— token manquant / invalide / expiré403— compte banni / action interdite404— ressource ou route inconnue405— méthode non autorisée422— attribut manquant ou invalide500— erreur serveur (détails uniquement siAPP_DEBUG=true)
- Attribut
level(ressourceusers) : entier dérivé deexperiencevia l’inverse de la courbe quadratiquexp(N) = F · N · (N-1)avecF = USER_LEVEL_FACTOR(25 par défaut). Formule :level = floor((F + sqrt(F² + 4·F·experience)) / (2·F)). AvecF=25: L1 ≥ 0 XP, L2 ≥ 50, L3 ≥ 150, L4 ≥ 300, L5 ≥ 500, L6 ≥ 750… Le client ne doit jamais le calculer lui-même : l’attribut est toujours présent dans la ressourceusers. - Pagination & objet
links(JSON:API 1.1) : toute réponse de type collection expose un objetlinksau niveau racine du document :"links": {"self": "https://api.example/api/users/me/media?limit=20","first": "https://api.example/api/users/me/media?limit=20","prev": null,"next": "https://api.example/api/users/me/media?cursor=MTcxMjM0…&limit=20"}- Keyset (curseur opaque) — utilisé par tous les listings ordonnés sur un timestamp + UUID (
media,notifications,followers,following). Les paramètres sont :?cursor=<opaque>: page suivante (rows strictement plus anciennes que le curseur).?before=<opaque>: page précédente (rows strictement plus récentes que le curseur). Mutuellement exclusif avec?cursor;?beforegagne s’ils sont présents tous les deux.?limit=<int>: taille de page (bornée par endpoint, défaut 20).links.lastn’est pas émis (le keyset ne sait pas calculer la dernière page sans full scan).- Un curseur malformé renvoie
400 Invalid cursor(jamais de fallback silencieux).
- Offset — utilisé par les listings backend Meilisearch (
/api/users/search). Paramètres?offset=<int>+?limit=<int>. Les cinq liensself/first/prev/next/lastsont émis (le total estimé permet de calculerlast). - Membres nuls : les liens non disponibles (ex :
prevsur la première page) restent présents avec la valeurnull— c’est conforme JSON:API et permet au client de distinguer « pas de page précédente » de « endpoint sans pagination ». - Collections non paginées (
/api/auth/sessions,/api/users/me/oauth-identities,/api/i18n/locales) : seullinks.selfest émis.
- Keyset (curseur opaque) — utilisé par tous les listings ordonnés sur un timestamp + UUID (
meta.total— compte total d’éléments d’une collection : quand le total est calculable à coût borné (compteur dénormalisé suruser_stats,COUNT(*)indexé paruser_id, etc.), il est exposé dansmeta.totalaux côtés demeta.limit. Sémantique :- Exact (entier,
meta.total) : endpoints keyset adossés à MySQL (/api/users/me/media,/api/users/{id}/media,/api/users/me/notifications,/api/users/{id}/followers|followinget leurs variantesme). Le compte reflète le même filtre que la requête (ex :meta.totalde/api/users/me/notifications?filter=unreadne compte que les non-lues). - Estimé (entier,
meta.totalHits) : endpoints offset adossés à Meilisearch (/api/users/search,/api/media/nearby,/api/media/in-bounds). Le moteur retourne une estimation rapide ; ne pas s’en servir pour des assertions strictes côté client. - Absent : sur les collections non paginées (
links.selfonly) — le client peut utiliserdata.length.
- Exact (entier,
- Format des dates (toutes les ressources JSON:API) : tout champ date/datetime exposé par l’API publique est un objet structuré, jamais une chaîne ISO brute. Forme canonique :
"createdAt": {"iso": "2026-06-14T08:42:13+00:00","formatted": {"long": "14 juin 2026 08:42","short": "14/06/2026 08:42","dateOnly": "14 juin 2026","time": "08:42"},"relative": "il y a 3 minutes"}
- Fuseau :
isoest toujours en UTC (+00:00). Les rendusformatted.*etrelativesont localisés via la même locale que la ressource (en-têteAccept-Languagedu viewer, normalisée parLocaleResolverMiddleware). relative: généré côté serveur viaCarbon::diffForHumans(). Le client peut l’afficher tel quel ; aucun calcul à faire pour « il y a X jours ».formatted.*: utilisent les tokensLL/LLL/L LT/LTde Carbon — la sortie suit la locale (FR :14 juin 2026, EN :June 14, 2026).- Valeur nulle : l’attribut est
null(jamais un objet vide). Le client doit donc testerif (createdAt !== null)avant d’accéder àiso/formatted/relative. - Champs concernés : tous les
createdAt,updatedAt,confirmedAt,joinedAt,bannedUntil,profileCompletedAt,birthdate,shotAt,readAt,editedAt,deletedAt,linkedAt,earnedAt,expiresAt,lastUsedAt,cooldownUntil,followedAt,reactedAt, etc. - Hors-périmètre : les curseurs de pagination restent des chaînes opaques (encodage interne) ; l’API admin (
/admin/*) reste plate (string ISO simple, pour Talend/Postman).
- Fuseau :