Livrable 11
API
Style
- Préfixe
/api/v1. - REST. JSON. Erreurs typées
{ "error": { "code", "message" } }— message sûr pour l'utilisateur, détail en log serveur. - Pagination curseur
{ "data", "next_cursor" }sur les listes. - Auth : session cookie httpOnly, Secure, SameSite (Better Auth) quand le compte existe. Les routes publiques ne demandent pas de session.
- Autorisation dans le handler, jamais seulement dans le routeur UI.
- Validation Zod sur chaque body et query.
- OpenAPI généré depuis ces schémas (livrable d'implémentation, pas de cette phase). Rate limit : voir déploiement (mémoire en un seul process dev ; Redis ou limite edge en prod —
THIRD-PARTYsi edge).
Surfaces
| Méthode | Chemin | Accès | Rôle |
|---|---|---|---|
| GET | /cities | public | villes published seulement. draft et soft_launch absents. |
| GET | /cities/:slug | public si published ; staff si soft_launch ; sinon 404 | DNA publiée + ancrages. soft_launch : noindex. |
| GET | /cities/:slug/navigation?at= | public | nav résolue à un instant |
| GET | /cities/:slug/zones | public | zones publiées |
| GET | /cities/:slug/now?zone=&bbox= | public | Now engine |
| GET | /cities/:slug/tonight | public | fenêtre locale « ce soir » |
| GET | /cities/:slug/weekend | public | fenêtre week-end |
| GET | /cities/:slug/search?q= | public | recherche ; noindex côté HTML |
| GET | /cities/:slug/venues/:venueSlug | public | fiche + bloc live |
| GET | /cities/:slug/events/:eventSlug | public | fiche |
| GET | /cities/:slug/map?bbox=&filters= | public | objets viewport, plafonnés |
| POST | /plans | public ou user | créer un plan P1 ; anonyme permis, éphémère |
| GET | /pro/today | member | compteurs réels |
| POST | /pro/live-updates | member APPROVED | publier |
| POST | /pro/events | member | studio |
| POST | /pro/offers | member | offre |
| POST | /pro/claims | user | demande |
| GET | /city/:cityId/quality | city staff | comptes qualité |
| PUT | /city/:cityId/dna | city manager | brouillon |
| POST | /city/:cityId/dna/publish | city manager | publier |
| POST | /city/:cityId/dna/rollback | city manager | revenir à une version stockée |
| POST | /admin/cities | super | wizard |
| POST | /reports | public limité | signalement |
Pas de /admin accessible par un rôle city. Pas d'ID d'une autre ville accepté « parce que le client l'envoie ».
Now
GET /now est aussi disponible en alias sous la ville. Réponse :
{
"evaluated_at": "ISO-8601",
"timezone": "Europe/Paris",
"definition": "open_now | event_active | event_starting_soon | offer_active, non expirés",
"results": []
}Chaque item : id, type, title, status_temporel, distance_m ou distance_unknown, provenance, reasons[] courtes. Jamais de fréquentation.
event_starting_soon : time_windows.starting_soon_minutes de la DNA (défaut proposé 90, non validé). Pas une seconde constante dans le code.
Chaque item porte source_type, verification_status, temporal_status et provenance (phrase). Pas un badge unique. distance_m seulement avec une origine ; sinon distance_unknown: true. Filtre distance absent sans origine.
Search
Query : q, zone, when (now|tonight|weekend|range), category, open_now, bbox, limit.
Réponse : results, interpreted_intent (peut être nul si non parsé), gaps non exposé au public.
Erreurs
| code | quand |
|---|---|
validation_error | payload |
not_found | slug inconnu ou autre ville |
forbidden | rôle |
unverified_claim | publish sans claim approuvé |
rate_limited | abus |
dependency_unavailable | tuile, routeur, IA — la ressource principale reste servie si possible |
legal_hold | objet alcool public alors que flag off |
Décisions
D1 — REST unique, pas de BFF par produit
- Choix : une API, trois clients (pages) dans la même app.
- Alternatives : GraphQL ; trois API.
- Compromis : over-fetch évité par des DTO explicites (
VenueCard,VenueDetail), pas par un graphe libre. - Multi-ville : le slug de ville est dans le chemin public. L'admin utilise l'UUID.
D2 — Écritures pro idempotentes
Idempotency-Key sur publish, claim, campagne, webhook Stripe. Double tap ≠ double happy hour.
D3 — Contrats avant UI large
Les DTO de Now, Search, VenueCard, LiveUpdate sont spécifiés avant les écrans. L'UI ne devine pas un champ.
Flags
- OpenAPI : à générer à l'implémentation, pas un fichier fictif « déjà publié ».
- Webhooks Stripe :
THIRD-PARTY, signature obligatoire, hors MVP fonctionnel.