> ⚠️ **MODE DEV STRICT (pas PROD)**  
> - **Aucune transition**, **aucun fallback**, **aucune rétro-compat**.  
> - Soit on règle un point proprement soit on n'y touche pas. pas de zone de : "transition ou entre deux".  
> - Toute contribution qui introduit une zone grise, un compromis ou un retour au legacy est **refusée**.

# Contrat HTTP/JSON canonique (V4)

Objectif: un seul contrat public, simple et uniforme, pour toutes les réponses API.

## Forme standard

- Succès
  - `200..299 { "ok": true, ...payload }`
  - Payload libre (ex: `boards`, `board`, `files`, `user`, `revision`…), mais toujours avec `ok: true`.

- Erreur
  - `4xx/5xx { "ok": false, "code": "UPPER_SNAKE", "message": "Lisible", "details"?: {...} }`
  - `code` = symbolique, stable; `message` = texte humain exploitable.
  - `details` = optionnel (ex: erreurs de validation).

## Statuts HTTP (recommandations)

- `401 UNAUTHORIZED` → `{ ok:false, code: UNAUTHORIZED, message }` (session requise)
- `403 FORBIDDEN` → `{ ok:false, code: FORBIDDEN, message }` (droits insuffisants / CSRF invalide)
- `404 NOT_FOUND` → `{ ok:false, code: NOT_FOUND, message }` (ressource inconnue)
- `409 <X>_CONFLICT` → `{ ok:false, code: BOARD_CONFLICT|..., message, history? }`
- `412 PRECONDITION_FAILED` → `{ ok:false, code: PRECONDITION_FAILED, message }` (ETag / If-Match)
- `413 PAYLOAD_TOO_LARGE` → `{ ok:false, code: PAYLOAD_TOO_LARGE, message }`
- `422 INVALID_PAYLOAD` → `{ ok:false, code: INVALID_PAYLOAD, message, details? }` (validation)
- `422 INVARIANT_BROKEN` → `{ ok:false, code: INVARIANT_BROKEN, message, details? }` (invariants métiers)
- `429 RATE_LIMITED` → `{ ok:false, code: RATE_LIMITED, message }`
- `5xx INTERNAL_SERVER_ERROR` → `{ ok:false, code: INTERNAL_SERVER_ERROR, message }` (texte générique en prod)

### Exemples

- Conflit (autosave)
```json
{ "ok": false, "code": "BOARD_CONFLICT", "message": "...", "history": [ ... ] }
```

- CSRF manquant
```json
{ "ok": false, "code": "CSRF_SESSION_REQUIRED", "message": "Session requise pour valider la requête." }
```

## En‑têtes et encodage

- Toujours `Content-Type: application/json; charset=utf-8` (sauf binaire).
- UTF‑8 sans échappement des slashs (`JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES`).
- Binaire: voir `files-attachments.md`.

## Point d’unification serveur

- Utiliser `Response::ok($payload, $status=200, $headers=[])` pour les succès.
- Utiliser `Response::error($code, $message, $details=[], $status=4xx/5xx)` pour les erreurs.
- `Response::json(...)` est un **encodeur brut** (aucune normalisation d’enveloppe) — ne pas l’utiliser pour les réponses applicatives publiques.

## Bonnes pratiques contrôleurs

- Toujours retourner via `Response::ok(...)` ou `Response::error(...)`.
- En succès, passer par `Response::ok` (inclut `ok:true`).
- En erreur, `Response::error(code,message,details?,status)` ; **jamais** d’HTML brut.

