# API Workspace V1

Base path : `/api/v1`

## En-têtes communs

- `Authorization: Bearer <access_token>` pour les routes authentifiées.
- `X-Client-Instance-UUID: <uuid>` obligatoire avec les tokens PLACE.
- `X-TRAILDPS-Client-Version: <version>` recommandé.
- `X-Request-ID: <uuid>` accepté ou généré par le serveur.
- `Idempotency-Key: <uuid>` obligatoire pour les créations, révisions, publications, inventaires, uploads et restaurations.

## Authentification et utilisateurs

| Méthode | Route | Objet |
|---|---|---|
| POST | `/auth/login` | Connexion workspace et contexte. |
| POST | `/auth/refresh` | Rotation du refresh token. |
| POST | `/auth/logout` | Révocation du refresh token. |
| POST | `/auth/invitations/accept` | Acceptation d'une invitation. |
| POST | `/auth/password-reset/confirm` | Confirmation d'un reset administré. |
| GET | `/me/context` | Rôles, permissions, entitlements et profil de contact. |
| PATCH | `/me/profile` | Mise à jour du nom affiché et du téléphone professionnel facultatif. |

Le bloc `context` renvoyé par `login`, `refresh` et `me/context` contient notamment `organization_uuid`, `subscription_uuid` et `client_instance_uuid`. L’ajout de `subscription_uuid` en version 1.0.7-lot1 est additif et permet à PLACE de produire l’inventaire initial des courses sans valeur codée en dur.
| GET | `/organizations` | Organisations accessibles. |
| POST | `/organizations/{organization_uuid}/invitations` | Invitation d'un utilisateur. |
| GET | `/organizations/{organization_uuid}/users` | Liste des utilisateurs. |
| POST | `/organizations/{organization_uuid}/users/{user_uuid}/roles` | Remplacement contrôlé des rôles. |
| DELETE | `/organizations/{organization_uuid}/users/{user_uuid}` | Révocation de l'appartenance et des sessions. |
| POST | `/organizations/{organization_uuid}/users/{user_uuid}/password-reset` | Création d'un jeton de reset de 30 minutes. |

## Événements et révisions

| Méthode | Route | Objet |
|---|---|---|
| GET | `/events` | Liste filtrée des événements. |
| POST | `/events` | Réservation ou création d'un `event_uuid`. |
| GET | `/events/{event_uuid}` | Métadonnées et historique des révisions. |
| POST | `/events/{event_uuid}/revisions` | Publication d'un snapshot préparatoire. |
| GET | `/events/{event_uuid}/snapshot?revision=n` | Téléchargement d'un snapshot contrôlé. |
| GET | `/events/{event_uuid}/changes?since_revision=n` | Manifest des changements. |
| POST | `/events/{event_uuid}/publish` | Marquage d'une révision publiée. |
| POST | `/events/{event_uuid}/resolve-conflict` | Création et publication atomiques d'une révision issue d'un arbitrage humain. |
| POST | `/events/{event_uuid}/lock` | Acquisition ou renouvellement du verrou consultatif. |
| DELETE | `/events/{event_uuid}/lock` | Libération du verrou. |
| GET | `/events/{event_uuid}/audit` | Audit métier filtré. |

Le corps d'une révision respecte `schema_version=sas_sync_v1`, fournit `base_revision` et contient un snapshot complet. Toute divergence de `base_revision` répond `409 REVISION_CONFLICT`. Aucune fusion silencieuse n'est effectuée.

Depuis `1.0.9-lot1`, les réponses de création et de lecture d'un snapshot exposent `content_sha256`. Cette empreinte canonique exclut uniquement `base_revision`, qui reste contrôlée avant toute écriture. Si le contenu métier est identique à la révision courante, le serveur retourne `identical=true` et ne crée aucune révision supplémentaire. `snapshot_sha256` reste l'empreinte d'intégrité du payload complet stocké.

La publication accepte le champ additif `expected_current_revision`. PLACE L2.6 le transmet systématiquement ; si la révision courante a changé entre la comparaison et la publication, le serveur répond `409 REVISION_CONFLICT` et ne publie aucune révision obsolète.


Depuis `1.0.12-lot1`, la route `changes` enrichit chaque objet modifié avec l’auteur de la révision, la date, le poste client et, lorsqu’il a été volontairement renseigné, le téléphone professionnel. Ces informations sont réservées aux membres autorisés de la même organisation et ne sont pas intégrées au snapshot métier.

L’arbitrage de conflit exige la permission `snapshot.resolve_conflict`, limitée à `ADMIN_ORGA` et `RESPONSABLE_DPS`. PLACE transmet la dernière révision commune, la révision distante attendue, son empreinte et une décision explicite pour chaque contradiction. Le serveur revalide la concurrence, crée une nouvelle révision, la publie dans la même transaction et journalise uniquement les choix et les empreintes des valeurs, jamais les valeurs métier en clair. Une évolution concurrente pendant l’arbitrage renvoie `409 REVISION_CONFLICT` ou `409 STALE_CONFLICT_PREVIEW`.

Depuis `1.0.10-lot1`, `poi_course_links.first_runner_time` et `last_runner_time` acceptent les formes historiques PLACE `HH:MM` / `HH:MM:SS` ainsi que les dates-heures complètes ISO. Lors de la projection MySQL, une heure seule est rattachée à la date de la course ; un passage antérieur à l'heure de départ est porté à J+1 et une fin de fenêtre antérieure à son début est portée au jour suivant. Le snapshot brut et ses empreintes restent inchangés. Un format non reconnu produit désormais une erreur structurée `422 BUSINESS_RULE_VIOLATION` au lieu d'une erreur SQL interne.

## Photos POI

| Méthode | Route | Objet |
|---|---|---|
| GET | `/files/exists?sha256=` | Détection d'un hash dans l'organisation. |
| POST | `/events/{event_uuid}/pois/{poi_uuid}/photos` | Upload/remplacement multipart : `photo`, `expected_event_revision`, `expected_poi_version`. |
| GET | `/events/{event_uuid}/pois/{poi_uuid}/photos` | Historique des versions. |
| GET | `/files/{file_uuid}/preview` | Affichage inline contrôlé. |
| GET | `/files/{file_uuid}/download` | Téléchargement contrôlé. |
| POST | `/events/{event_uuid}/pois/{poi_uuid}/photos/{file_uuid}/trash` | Mise en corbeille versionnée et idempotente ; versions attendues dans le JSON. |
| DELETE | `/events/{event_uuid}/pois/{poi_uuid}/photos/{file_uuid}` | Route historique de mise en corbeille, conservée pour compatibilité. |
| POST | `/events/{event_uuid}/pois/{poi_uuid}/photos/{file_uuid}/restore` | Restauration transactionnelle avec versions attendues dans le JSON. |

Formats autorisés : JPEG et PNG. Limites par défaut : 10 Mo et 8 000 x 8 000 px.

Pour un upload ou un remplacement multipart :

```text
photo=@fichier.png
expected_event_revision=2
expected_poi_version=1
```

Pour une restauration :

```json
{
  "expected_event_revision": 3,
  "expected_poi_version": 2
}
```

Toute divergence entre les versions attendues et les versions verrouillées en base répond `409 REVISION_CONFLICT`. Une opération réussie crée dans la même transaction une nouvelle révision de l’événement, incrémente `pois.object_version` et actualise `photo_metadata` dans le snapshot.

Depuis `1.0.13-lot1`, PLACE utilise également la suppression versionnée :

```json
{
  "expected_event_revision": 4,
  "expected_poi_version": 2
}
```

Le serveur vérifie en outre que chaque entrée `photo_metadata` d'une nouvelle révision correspond à un fichier actif réellement présent dans `shared_files` et à une version courante de `poi_photo_versions`, avec le même POI, le même hash, le même type MIME, la même taille, les mêmes dimensions et le même numéro de version. Une métadonnée non adossée au stockage privé est refusée avec `HTTP 422 PHOTO_METADATA_NOT_BACKED`. Cette règle empêche qu'une révision JSON ou un arbitrage référence un binaire inexistant.

## Usage et migration

| Méthode | Route | Objet |
|---|---|---|
| GET | `/subscriptions/{subscription_uuid}/collaborative-usage` | Courses, stockage, users et fraîcheur. |
| POST | `/migration/course-inventory` | Inventaire initial des UUID par poste ; `Idempotency-Key` obligatoire. |
| GET | `/health` | État de l'API et de la connexion SQL. |

La route d’inventaire est idempotente depuis `1.0.8-lot1` : un rejeu avec la même clé et le même corps restitue la même preuve, et la déduplication par hash retourne toujours l’`inventory_uuid` réellement conservé.

Le champ `sms_used` reste indisponible dans le LOT 1. Son alimentation consolidée appartient au LOT 3 Licence/Tracking.

## Format d'erreur

```json
{
  "ok": false,
  "error": {
    "code": "REVISION_CONFLICT",
    "message": "La révision serveur a évolué.",
    "details": {"expected": 7, "current": 8}
  },
  "request_id": "...",
  "schema_version": "workspace_api_v1",
  "server_time_utc": "...Z"
}
```
Depuis `1.0.11-lot1`, le serveur vérifie obligatoirement le bloc `checksums` avant toute création de révision. Chaque empreinte doit être le SHA-256 du JSON canonique du bloc métier correspondant (`event`, collections préparatoires et `photo_metadata`). Une preuve absente ou incohérente renvoie `HTTP 422 SNAPSHOT_CHECKSUM_MISMATCH` avant toute projection MySQL. Le script `scripts/repair_snapshot_checksums.php` permet de diagnostiquer un snapshot historique incohérent et, avec `--apply`, de créer une nouvelle révision corrective immuable sans réécrire l'historique ni modifier la révision publiée.

## L2.10.7 — Catalogue et suppression définitive

- `GET /events` renvoie désormais `events` et `deletions`.
- `GET /events/{event_uuid}/deletion-preview` requiert `event.delete`.
- `DELETE /events/{event_uuid}` requiert `event.delete`, une `Idempotency-Key`, `expected_revision`, `confirm_irreversible=true` et la phrase de confirmation exacte.
- Toute route adressant un UUID supprimé renvoie `410 EVENT_DELETED` avec le reçu minimal disponible.
- La création d'un événement avec un UUID présent dans `event_deletions` est refusée par le même code `410`.


## Extension additive LOT 2.10.8 — vitesses de course

Chaque objet de la collection `courses` peut contenir :

- `first_runner_speed_kmh` : nombre ou `null`, de 0,1 à 99,9 ;
- `last_runner_speed_kmh` : nombre ou `null`, de 0,1 à 99,9.

Les champs sont facultatifs pour préserver la compatibilité avec les clients antérieurs.
