Skip to content
Ilan Amzallag edited this page Sep 23, 2026 · 29 revisions

API

Statut : 🟡 Brouillon · Màj : 2026-09-21 · Tickets : —

Contrat du MVP. Préfixe /v1. Authentification par jeton Firebase quand indiqué. Les schémas Zod dans packages/shared font foi ; cette page en est le résumé lisible.

Conventions

  • Pagination par curseur : ?cursor=&limit= (max 50), réponse { items, nextCursor }.
  • Erreurs : { code, message, details? }, codes HTTP standards. Codes métier stables : validation_error, unauthorized, forbidden, not_found, conflict, rate_limited, coherence_blocked, quota_exceeded, premium_required (ces deux derniers en 402, avec details.limit et details.reason pour alimenter E-17).
  • Dates ISO 8601 UTC ; coordonnées { lat, lng }.
  • Toutes les lectures publiques sont cachables (Cache-Control court, ETag).

Découverte (public)

Méthode Route Description
GET /routes/search Parcours dans une zone avec filtres. Query : bbox=w,s,e,n, audiences[], moods[], conditions[], durations[], budgets[], transports[], `sort=recommended
GET /users/:id/profile Profil public : avatar, prénom, membre depuis, note moyenne, parcours publiés (cards). 404 si is_public = false
GET /routes/:id/ratings Avis visibles, paginés
GET /routes/search/count Même query, réponse { count } pour le compteur des filtres. breakdown=all_but_one ajoute without : le nombre de parcours obtenu en retirant chaque filtre actif pris isolément, groupe par groupe et jamais valeur par valeur, pour les suggestions du zéro résultat de E-03 (D-010)
GET /routes/:id Fiche complète (étapes, lieux, horaires du jour, computed, vérification, lieux signalés). ?token= pour un parcours privé. Parcours access = premium pour un appelant non abonné : étape 1 complète, étapes suivantes réduites à heure, catégorie, durée et trajet, sans lieu, coordonnées ni conseil, et pas de tracé (D-014). Parcours retiré : 410 avec la date du retrait
GET /routes/:id/timeline Query startsAt= : timeline recalculée et conflits
GET /geocode Query q=, proxy Mapbox Geocoding restreint FR, avec cache
GET /weather/today Query city=paris : `{ condition: 'rainy'
GET /config Taxonomies, libellés, seuils publics, version minimale de l'app

Compte (auth)

Méthode Route Description
GET /me Profil, rôle, plan, préférences, compteurs du mois (programmations, favoris) et limites applicables
PATCH /me Prénom, avatar, isPublic, préférences de notification
POST /webhooks/revenuecat Webhook signé : met à jour plan et plan_expires_at
DELETE /me Suppression de compte (anonymisation ou purge selon mode)
POST /me/devices Enregistrement du jeton push

Favoris et programmation (auth)

Méthode Route Description
GET /me/favorites
PUT / DELETE /me/favorites/:routeId 402 quota_exceeded au-delà de la limite gratuite
GET /me/planned Query status=
POST /me/planned { routeId, startsAt } → programmation avec timeline et checklist. 402 quota_exceeded ou premium_required (parcours Premium)
POST /me/planned/:id/rating { rating, comment? } ; exige un Go terminé ; crée ou met à jour route_ratings
PATCH /me/planned/:id startsAt, checklist, status (in_progress, completed, cancelled), personalNote
POST /me/planned/:id/steps/:stepId/done Coche une étape (Go) ; corps facultatif { signal: { kind, reason?, comment? } } pour la vérification sur place
GET /me/planned/:id/maps-url URL Google Maps construite côté serveur (règle de limite de points)

Création (auth)

Méthode Route Description
GET /me/routes Mes parcours, tous statuts
POST /routes Crée un brouillon
PATCH /routes/:id Informations, ambiances, publics, conditions, transport
PUT /routes/:id/steps Remplace la liste ordonnée des étapes ; retourne le résultat d'orchestration (computed, warnings, errors)
POST /routes/:id/submit Passe en pending ; 422 coherence_blocked si règles bloquantes
POST /routes/:id/status private, draft, unpublished par l'auteur
POST /media/upload-url { ownerType, ownerId, contentType } → URL signée et mediaId
DELETE /media/:id

Lieux (auth)

Méthode Route Description
GET /places/search Query q=, near=, category= : lieux Widoo existants
POST /places Création ; répond 409 avec le candidat si un lieu proche de même nom existe, sauf force=true
PATCH /places/:id Auteur du lieu tant qu'il n'est utilisé que par ses parcours ; sinon modérateur
POST /places/:id/signals `{ kind: 'confirm'

Admin (rôle moderator ou admin)

Méthode Route Description
GET /admin/moderation File pending et changes_requested
POST /admin/routes/:id/publish
POST /admin/routes/:id/request-changes { message }
POST /admin/routes/:id/reject { reasonCode, message }
POST /admin/routes/:id/unpublish
GET /admin/signals Groupés par lieu
POST /admin/places/:id/verify Remet verified, purge les signaux ouverts
POST /admin/places/:id/close Passe closed, parcours concernés en needs_fix
POST /admin/signals/:id/reject
POST /admin/ratings/:id/hide Masque un commentaire
POST /internal/recommendation Recalcul nocturne des composantes stockées du score
GET /admin/stats Compteurs du tableau de bord
POST /admin/places/import CSV, rôle admin

Interne (jeton de service, Cloud Scheduler)

Méthode Route Description
POST /internal/recompute Recalcul des parcours dont un lieu a changé
POST /internal/reminders Rappels J-1 et H-1 des parcours programmés
POST /internal/verification-decay Passage verified → stale après 90 jours

Clone this wiki locally