Skip to content

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-Type réponse : application/vnd.api+json
    • Erreurs : tableau errors[] avec status, title, detail, optionnel source.pointer et meta
  • Authentification : Bearer token via header Authorization: Bearer <token> Le token est obtenu via POST /api/auth/login et a une durée de vie glissante de 30 jours (renouvelée à chaque requête authentifiée).
  • Codes d’erreur communs :
    • 400 — corps JSON invalide
    • 401 — token manquant / invalide / expiré
    • 403 — compte banni / action interdite
    • 404 — ressource ou route inconnue
    • 405 — méthode non autorisée
    • 422 — attribut manquant ou invalide
    • 500 — erreur serveur (détails uniquement si APP_DEBUG=true)
  • Attribut level (ressource users) : entier dérivé de experience via l’inverse de la courbe quadratique xp(N) = F · N · (N-1) avec F = USER_LEVEL_FACTOR (25 par défaut). Formule : level = floor((F + sqrt(F² + 4·F·experience)) / (2·F)). Avec F=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 ressource users.
  • Pagination & objet links (JSON:API 1.1) : toute réponse de type collection expose un objet links au 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 ; ?before gagne s’ils sont présents tous les deux.
      • ?limit=<int> : taille de page (bornée par endpoint, défaut 20).
      • links.last n’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 liens self/first/prev/next/last sont émis (le total estimé permet de calculer last).
    • Membres nuls : les liens non disponibles (ex : prev sur la première page) restent présents avec la valeur null — 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) : seul links.self est émis.
  • meta.total — compte total d’éléments d’une collection : quand le total est calculable à coût borné (compteur dénormalisé sur user_stats, COUNT(*) indexé par user_id, etc.), il est exposé dans meta.total aux côtés de meta.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|following et leurs variantes me). Le compte reflète le même filtre que la requête (ex : meta.total de /api/users/me/notifications?filter=unread ne 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.self only) — le client peut utiliser data.length.
  • 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 : iso est toujours en UTC (+00:00). Les rendus formatted.* et relative sont localisés via la même locale que la ressource (en-tête Accept-Language du viewer, normalisée par LocaleResolverMiddleware).
    • relative : généré côté serveur via Carbon::diffForHumans(). Le client peut l’afficher tel quel ; aucun calcul à faire pour « il y a X jours ».
    • formatted.* : utilisent les tokens LL/LLL/L LT/LT de 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 tester if (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).