Aller au contenu

Contrats API HomyExpert

Ce document est la référence des contrats de l'API REST HomyExpert pour les développeurs front (mobile Flutter + web Nuxt). Il permet de coder contre un contrat connu — payloads, réponses, codes d'erreur — sans attendre que chaque endpoint backend soit livré.

Il est dérivé du backlog backend (EP-BACK-01 → 25) et, pour les endpoints déjà déployés, du code réel observable sur le staging.

Légende des statuts

  • 🟢 Livré — endpoint codé et déployé sur le staging. Le contrat est stable et reflète le code réel. Consultable en live : https://hme-backend.devmix.tech/api/docs
  • 🟡 À valider — contrat anticipé depuis le backlog, pas encore codé. À valider par le PO avant que le front code dessus ; susceptible d'ajustement.
  • ⚙️ Stub — pour les EP-BACK-08 à 22, l'endpoint est déjà exposé en ligne comme bouchon : visible dans /api/docs, il accepte les appels et renvoie une réponse d'exemple conforme au contrat, avec l'en-tête X-Api-Stub: true. Le front peut câbler dessus immédiatement ; la logique métier réelle remplacera le bouchon à la livraison de l'épique. Le statut du contrat reste 🟡 tant que le PO ne l'a pas validé.

Conventions transverses

Ces règles s'appliquent à tous les endpoints (établies en EP-BACK-01).

  • Préfixe : toutes les routes sont versionnées sous /api/v1.
  • Format : JSON. Header Accept: application/json recommandé sur toutes les requêtes.
  • Identifiants : les ressources exposées dans les URLs et payloads utilisent des identifiants opaques non séquentiels (ULID 26 caractères pour les modèles métier, users exposé via une route key publique). Aucun entier auto-incrément ne fuite dans l'API.
  • Auth : Sanctum, token bearer (Authorization: Bearer <token>). Trois régimes :
  • public — aucun token requis
  • auth:sanctum — token utilisateur strict
  • auth.dual — accepte un token utilisateur ou un token de session invité
  • Erreurs : enveloppe constante { message, errors? }. Le champ errors n'apparaît que pour les 422 (validation Laravel). Mapping figé :
  • 401 — « Unauthenticated. »
  • 403 — « Forbidden. »
  • 404 — « Resource not found. »
  • 422 — erreur de validation, avec errors détaillé par champ
  • 500 — « Server Error. »
  • Pagination : page-based standard Laravel — ?page=N&per_page=N (défaut per_page=20). Enveloppe : json { "data": [], "meta": { "current_page": 1, "per_page": 20, "total": 0, "last_page": 1 }, "links": { "first": "...", "last": "...", "prev": null, "next": null } }

Table des matières

EP Titre Statut
EP-BACK-01 Cadrage : init et architecture 🟢
EP-BACK-02 Modèle de données 🟢 (interne)
EP-BACK-03 Auth et sessions (invité + authentifié) 🟢
EP-BACK-04 Médias : storage et livraison 🟢
EP-BACK-05 Notifications transverses 🟢
EP-BACK-06 API Onboarding (dashboard + funnel) 🟢
EP-BACK-07 API Tronc commun DDE (Q1→Q6) 🟢
EP-BACK-08 API Statut occupant 🟡
EP-BACK-09 API Branche Chez moi : Cause + Travaux 🟡
EP-BACK-10 API Phénomène naturel + georisques 🟡
EP-BACK-11 API Cause = Aucune des deux 🟡
EP-BACK-12 Routage Dommages-Ouvrage 🟡
EP-BACK-13 Abonnement Stripe (persona F) 🟡
EP-BACK-14 API Branches Tiers↔moi 🟡
EP-BACK-15 Mise en demeure entreprise 🟢
EP-BACK-16 Génération constat amiable + rapport 🟡
EP-BACK-17 Signature électronique 🟢
EP-BACK-18 Analyse IA et estimation 🟡
EP-BACK-19 Envoi LRE via AR24 🟡
EP-BACK-20bis Contact-Support (remplace VOE Conseil) 🟢
EP-BACK-21 Escalade (parcours-fin-2) 🟢
EP-BACK-22 Super administration (placeholder) 🟡
EP-BACK-23 Observabilité et monitoring 🟢 / 🟡
EP-BACK-24 Internationalisation (i18n) 🟡 (transverse)
EP-BACK-25 Provisioning infrastructure 🟡 (hors API)

EP-BACK-01 — Cadrage, init et architecture

Endpoints de fondation : santé applicative et documentation.

GET /api/v1/health 🟢 Livré

Liveness ultra-rapide (< 100 ms), utilisée par la sonde de démarrage Cloud Run.

  • Auth : public
  • Requête : aucune
  • Réponse (200) : json { "status": "ok", "version": "0.12.0", "environment": "staging", "timestamp": "2026-05-18T15:34:45+00:00" }
  • Erreurs : aucune notable. L'endpoint ne touche ni la DB ni les services tiers.

GET /api/docs 🟢 Livré

Documentation API auto-générée (UI Scribe + spec OpenAPI 3).

  • Auth : public en dev/staging ; désactivé (404) en prod.
  • Requête : aucune.
  • Réponse : page HTML interactive.

EP-BACK-02 — Modèle de données

Pas d'endpoint propre — cet EP livre les migrations et modèles Eloquent. Les structures ci-dessous sont réutilisées dans les réponses de tous les EPs suivants. Référence pour les fronts.

Glossaire métier FR ↔ identifiant technique EN

Concept métier (FR) Identifiant technique (EN)
sinistre claim
pièce room
partie touchée impact_area
dommage damage
contact contact
contrat d'assurance insurance_policy
document généré document
envoi (LRE / email) dispatch
session invité guest_session
foyer + collaborateurs team / team_members

Objet Claim (forme de base)

json { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "case_number": "HME-2026-000123", "status": "DRAFT", "type": "WATER_DAMAGE", "origin": "OWN_PROPERTY", "cause": "CONSTRUCTION_WORK", "created_at": "2026-05-18T10:00:00+00:00", "signed_at": null, "sent_at": null }

Enums du domaine

Enum Valeurs
ClaimType WATER_DAMAGE (seul implémenté V1), FIRE, BURGLARY, NATURAL_DISASTER, OTHER
ClaimOrigin OWN_PROPERTY, THIRD_PARTY_TO_ME, ME_TO_THIRD_PARTY
ClaimCause CONSTRUCTION_WORK, NATURAL_EVENT, NONE
ClaimWorkflowStatus DRAFT, SIGNED, SENT, IN_PROGRESS, CLOSED (+ statuts de qualification intermédiaires, cf. EP-09)
ImpactAreaType WALL, FLOOR, CEILING, ELECTRICAL_APPLIANCE, OTHER
DamageZone INTERIOR, EXTERIOR
ContactRole TENANT, OWNER, AGENCY, THIRD_PARTY, THIRD_PARTY_INJURED, THIRD_PARTY_VICTIM, CONSTRUCTION_COMPANY, BUILDER_DEVELOPER, INJURED_THIRD_PARTY
InsurancePolicyType HOME_INSURANCE_OCCUPANT, HOME_INSURANCE_NON_OCCUPANT, HOME_INSURANCE_THIRD_PARTY, CONSTRUCTION_DAMAGE_INSURANCE, PROFESSIONAL_LIABILITY, TEN_YEAR_WARRANTY
DocumentType AMICABLE_REPORT, FORMAL_NOTICE, DETAILED_REPORT
DispatchMode REGISTERED_LETTER, EMAIL
DispatchStatus CREATED, SENT, DELIVERED, ACKNOWLEDGED, FAILED
Locale fr, en, es, de, it (V1 : fr seul actif)

EP-BACK-03 — Auth et sessions

Démarrage en mode invité, inscription, login, SSO, conversion invité → compte.

POST /api/v1/guest-sessions 🟢 Livré

Démarre une session invité. Le token retourné sert de bearer pour toutes les opérations du parcours en mode invité.

  • Auth : public (rate-limité 10 créations / heure / IP)
  • Requête :
Champ Type Requis Notes
locale string enum Locale optionnel défaut détecté : payload > header Accept-Language > fr
  • Réponse (201) : json { "token": "9f3c1a...64-caracteres-url-safe", "expires_at": "2026-06-17T10:00:00+00:00", "locale": "fr" }
  • Erreurs :
  • 422locale non reconnue
  • 429 — plus de 10 créations sur la même IP dans l'heure

POST /api/v1/auth/register 🟢 Livré

Inscription email + mot de passe. Connecte immédiatement (token retourné) et envoie un mail de vérification en arrière-plan.

  • Auth : public (rate-limité 30 / 5 min / IP, partagé avec login et SSO)
  • Requête :
Champ Type Requis Notes
first_name string oui
last_name string oui
email string email oui unique
password string oui prod : 12 car. min, mixedCase + chiffres + symboles + non compromis ; local : 8 car. min
locale string enum Locale optionnel fallback fr
  • Réponse (201) : json { "token": "1|abcDEF...sanctum-token", "user": { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "first_name": "Marie", "last_name": "Dupont", "email": "marie@example.com", "email_verified": false, "locale": "fr" }, "was_recently_created": true }
  • Erreurs :
  • 422 — email déjà pris, email invalide, mot de passe trop faible, first_name manquant
  • 429 — rate-limit dépassé

GET /api/v1/auth/email/verify/{ulid}/{hash} 🟢 Livré

Vérification d'email via lien signé (présent dans le mail). Lien valide 60 min.

  • Auth : public, protégé par signature HMAC (middleware signed)
  • Requête : ulid et hash dans l'URL ; expires et signature en query string (générés par le backend dans le mail)
  • Réponse (200) : json { "message": "Email vérifié.", "email_verified_at": "2026-05-18T10:05:00+00:00" }
  • Erreurs :
  • 403 — signature invalide, lien expiré, ou hash ne correspond plus (email modifié entre-temps)
  • 404 — ULID inconnu

POST /api/v1/auth/email/resend 🟢 Livré

Renvoie le mail de vérification. Noop silencieux si l'email est déjà vérifié.

  • Auth : auth:sanctum
  • Requête : aucune
  • Réponse (200) : json { "message": "Email de vérification renvoyé." }
  • Erreurs :
  • 401 — non authentifié

POST /api/v1/auth/login 🟢 Livré

Login email + mot de passe. La réponse 401 est volontairement indistincte (email inconnu vs mauvais mot de passe) pour empêcher l'énumération des comptes.

  • Auth : public (rate-limité 30 / 5 min / IP)
  • Requête :
Champ Type Requis
email string email oui
password string oui
  • Réponse (201) : json { "token": "2|ghiJKL...sanctum-token", "user": { "id": "01JZ...", "first_name": "Marie", "last_name": "Dupont", "email": "marie@example.com", "email_verified": true, "locale": "fr" }, "was_recently_created": false }
  • Erreurs :
  • 401{ "message": "Identifiants invalides." } (mauvais mot de passe, email inconnu, ou compte SSO-only sans mot de passe)
  • 422 — payload invalide
  • 429 — rate-limit dépassé

POST /api/v1/auth/sso/apple 🟢 Livré

Sign in with Apple — mobile-native uniquement. Le SDK iOS fournit un identity_token (JWT signé Apple) que le backend vérifie contre les JWKS Apple.

  • Auth : public (rate-limité 30 / 5 min / IP)
  • Requête :
Champ Type Requis Notes
identity_token string (JWT) oui fourni par le SDK Apple
first_name string optionnel envoyé par Apple uniquement au 1er auth
last_name string optionnel idem
  • Réponse (201) : même forme que register ({ token, user, was_recently_created })
  • Erreurs :
  • 401identity_token invalide / expiré / mauvaise signature / mauvais issuer / mauvaise audience
  • 422identity_token manquant
  • 429 — rate-limit dépassé

POST /api/v1/auth/sso/google 🟢 Livré

Sign in with Google — mobile-native uniquement. Le SDK fournit un id_token (JWT signé Google).

  • Auth : public (rate-limité 30 / 5 min / IP, partagé avec Apple)
  • Requête :
Champ Type Requis Notes
id_token string (JWT) oui fourni par le SDK Google ; given_name / family_name extraits des claims
  • Réponse (201) : même forme que register
  • Erreurs :
  • 401id_token invalide / expiré / signature / issuer non reconnu / audience non listée
  • 422id_token manquant
  • 429 — rate-limit dépassé

POST /api/v1/auth/password/forgot 🟢 Livré

Demande de réinitialisation de mot de passe. Réponse uniforme que l'email existe ou non (anti-énumération).

  • Auth : public (rate-limité 30 / 5 min / IP)
  • Requête :
Champ Type Requis
email string email oui
  • Réponse (200) : json { "message": "Si un compte existe pour cette adresse, un email a été envoyé." }
  • Erreurs :
  • 422 — email invalide

POST /api/v1/auth/password/reset 🟢 Livré

Applique le nouveau mot de passe à partir du token reçu par mail (valide 60 min, usage unique).

  • Auth : public (rate-limité 30 / 5 min / IP)
  • Requête :
Champ Type Requis Notes
token string oui reçu dans le lien email
email string email oui
password string oui mêmes règles que register
  • Réponse (200) : json { "message": "Mot de passe réinitialisé." }
  • Erreurs :
  • 422 — token invalide / expiré / lié à un autre email, ou mot de passe trop faible

POST /api/v1/auth/attach-guest-session 🟢 Livré

Rattache une déclaration commencée en mode invité au compte de l'utilisateur connecté.

  • Auth : auth:sanctum strict (un guest token est refusé ici — anti-vol de session)
  • Requête :
Champ Type Requis
guest_token string oui
  • Réponse (200) : json { "attached": true, "guest_session_ulid": "01JZ8K2M7N4P6Q8R0S2T4V6W8X" }
  • Erreurs :
  • 401 — non authentifié, ou tentative d'auth via guest token
  • 404 — guest token inconnu / expiré / déjà consommé (indistinct, idempotent)
  • 422guest_token manquant

DELETE /api/v1/auth/session 🟢 Livré

Termine la session courante. Pour un utilisateur : révoque uniquement le token bearer présenté (les autres devices restent connectés). Pour une session invité : supprime la row guest_session.

  • Auth : auth.dual
  • Requête : aucune
  • Réponse : 204 No Content
  • Erreurs :
  • 401 — sans bearer, bearer invalide, ou 2e appel avec un token déjà révoqué

EP-BACK-04 — Médias

Upload direct client → Google Cloud Storage via URL signée (le binaire ne transite jamais par le backend). Stockage privé, lecture via URL signée expirante.

POST /api/v1/claims/{id}/media/upload-init 🟢 Livré

Étape 1/2 de l'upload : demande une URL signée V4 PUT vers GCS.

  • Auth : auth.dual
  • Requête :
Champ Type Requis Notes
type string enum oui PHOTO, VIDEO, LIDAR, PDF
mime string oui doit appartenir à la liste autorisée pour le type
size_bytes integer oui doit respecter la taille max du type
original_name string oui nom de fichier d'origine
room_id string ULID optionnel rattachement à une pièce
damage_id string ULID optionnel rattachement à un dommage

Limites par type : Photo image/jpeg,png,heic 20 Mo · Vidéo video/mp4,quicktime 200 Mo · LIDAR model/vnd.usdz+zip,gltf-binary 500 Mo · PDF application/pdf 20 Mo.

  • Réponse (200) : json { "upload_token": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "signed_url": "https://storage.googleapis.com/homyexpert-staging-media/claims/01JZ.../photos/...?X-Goog-Signature=...", "gcs_object": "claims/01JZ.../photos/01JZ8K....jpg", "expires_at": "2026-05-18T10:15:00+00:00" }
  • Erreurs :
  • 401 — non authentifié
  • 404 — sinistre inconnu ou non accessible
  • 422type non autorisé, mime incohérent, size_bytes au-dessus de la limite

POST /api/v1/claims/{id}/media/upload-complete 🟢 Livré

Étape 2/2 : notifie le backend après l'upload PUT direct. Le backend vérifie l'objet GCS (MIME + taille réels via HEAD) puis crée la ligne Media.

  • Auth : auth.dual
  • Requête :
Champ Type Requis
upload_token string oui
original_name string oui
gcs_object string oui
  • Réponse (201) : json { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "claim_id": "01JZ...", "room_id": null, "damage_id": null, "type": "PHOTO", "mime": "image/jpeg", "size_bytes": 1842311, "original_name": "salon-mur-nord.jpg", "status": "active", "thumbnail_object": null }
  • Erreurs :
  • 401 — non authentifié
  • 404 — sinistre ou upload_token inconnu
  • 422 — objet GCS absent, ou MIME/taille réels non conformes (l'objet est alors supprimé)

GET /api/v1/media/{id} 🟢 Livré

Retourne une URL signée V4 GET pour afficher ou télécharger le média (défaut 60 min).

  • Auth : auth.dual (Policy : owner du sinistre ou admin)
  • Requête : aucune
  • Réponse (200) : json { "signed_url": "https://storage.googleapis.com/homyexpert-staging-media/claims/.../photos/...?X-Goog-Signature=...", "expires_at": "2026-05-18T11:00:00+00:00" }
  • Erreurs :
  • 401 — non authentifié
  • 403 — utilisateur non propriétaire du sinistre
  • 404 — média inconnu

DELETE /api/v1/media/{id} 🟢 Livré

Marque le média comme supprimé et déclenche la suppression asynchrone de l'objet GCS et de sa vignette.

  • Auth : auth.dual (owner uniquement)
  • Requête : aucune
  • Réponse : 204 No Content
  • Erreurs :
  • 401 — non authentifié
  • 403 — utilisateur non propriétaire
  • 404 — média inconnu

EP-BACK-05 — Notifications transverses

Pipeline email / SMS / push (FCM) / in-app (Pusher). Endpoints exposés au front : enregistrement de device et liste des notifications in-app.

POST /api/v1/devices 🟢 Livré

Enregistre un device pour les push notifications FCM.

  • Auth : auth:sanctum
  • Requête :
Champ Type Requis Notes
token string oui token FCM du device
platform string enum oui ios, android
  • Réponse (201) : json { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "platform": "ios", "last_seen": "2026-05-18T10:00:00+00:00" }
  • Erreurs :
  • 401 — non authentifié
  • 422token ou platform manquant / invalide

GET /api/v1/notifications 🟢 Livré

Liste paginée des notifications in-app de l'utilisateur (canal database Laravel).

  • Auth : auth:sanctum
  • Requête : pagination standard ?page=N&per_page=N
  • Réponse (200) : json { "data": [ { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "type": "DispatchAcknowledgmentNotification", "data": { "claim_id": "01JZ...", "message": "Votre LRE a été distribuée." }, "read_at": null, "created_at": "2026-05-18T09:30:00+00:00" } ], "meta": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 }, "links": { "first": "...", "last": "...", "prev": null, "next": null } }
  • Erreurs :
  • 401 — non authentifié

POST /api/v1/notifications/{id}/read 🟢 Livré

Marque une notification comme lue.

Structure du verbe et du chemin anticipée : le backlog mentionne « endpoint mark-as-read » sans figer la route. À confirmer (alternative possible : PATCH /api/v1/notifications/{id}).

  • Auth : auth:sanctum
  • Requête : aucune
  • Réponse (200) : json { "id": "01JZ...", "read_at": "2026-05-18T10:00:00+00:00" }
  • Erreurs :
  • 401 — non authentifié
  • 404 — notification inconnue ou appartenant à un autre utilisateur

EP-BACK-06 — API Onboarding (dashboard + funnel)

Dashboard sinistres et funnel de qualification phase 1.

POST /api/v1/claims 🟢 Livré

Crée un sinistre DRAFT, en mode invité ou authentifié. Pas de payload — les questions du funnel sont enregistrées ensuite via PATCH.

  • Auth : auth.dual
  • Requête : aucune
  • Réponse (201) : json { "id": "01JZ8K2M7N4P6Q8R0S2T4V6W8X", "status": "DRAFT", "type": null, "origin": null, "cause": null }
  • Erreurs :
  • 401 — aucun token (ni utilisateur ni invité)

PATCH /api/v1/claims/{id}/funnel 🟢 Livré

Enregistre les choix du funnel phase 1 (type de bien, type de sinistre, qui est impacté). Sauvegarde partielle écran par écran : un champ absent ne réécrase jamais une valeur déjà persistée.

  • Auth : auth.dual
  • Requête (tous les champs optionnels — sauvegarde progressive) :
Champ Type Notes
property_kind string enum HOUSING, VEHICLE
claim_type string enum ClaimType WATER_DAMAGE, FIRE, BURGLARY, NATURAL_DISASTER, OTHER
impacted_party string enum calcule origin : OWN_PROPERTY / THIRD_PARTY_TO_ME / ME_TO_THIRD_PARTY
  • Réponse (200) : json { "id": "01JZ...", "status": "DRAFT", "type": "WATER_DAMAGE", "origin": "OWN_PROPERTY", "next_step": "COMMON_TRUNK" } next_stepINCOMPLETE (type de bien ou de sinistre manquant) · COMMON_TRUNK (Habitation + Dégât des eaux) · COMING_SOON (autre type de sinistre, hors V1).
  • Erreurs :
  • 401 — non authentifié
  • 404 — sinistre inconnu / non accessible
  • 422 — valeur d'enum invalide

GET /api/v1/claims 🟢 Livré

Liste paginée des sinistres de l'utilisateur authentifié.

  • Auth : auth:sanctum
  • Requête :
Param Type Notes
status string all (défaut), open, closed
page, per_page integer pagination standard
  • Réponse (200) : json { "data": [ { "id": "01JZ...", "case_number": "HME-2026-000123", "status": "DRAFT", "type": "WATER_DAMAGE", "origin": "OWN_PROPERTY", "created_at": "2026-05-18T10:00:00+00:00" } ], "meta": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 }, "links": { "first": "...", "last": "...", "prev": null, "next": null } } Les statistiques agrégées du dashboard ne sont plus dans meta : elles ont leur endpoint dédié GET /api/v1/statistics.
  • Erreurs :
  • 401 — non authentifié

GET /api/v1/claims/{id} 🟢 Livré

Détail d'un sinistre. Expose les champs propres au sinistre ; les relations sont incluses à la demande via ?include=.

  • Auth : auth.dual
  • Requête :
Param Type Notes
include string (CSV) relations à inclure : media, documents, dispatches. Liste blanche — une valeur inconnue est ignorée silencieusement.
  • Réponse (200) : json { "id": "01JZ...", "case_number": "HME-2026-000123", "status": "DRAFT", "type": "WATER_DAMAGE", "origin": "OWN_PROPERTY", "cause": null, "created_at": "2026-05-18T10:00:00+00:00", "signed_at": null, "sent_at": null, "media": [], "documents": [], "dispatches": [] } Les clés media / documents / dispatches n'apparaissent que si demandées via ?include=.
  • Erreurs :
  • 401 — non authentifié
  • 404 — sinistre inexistant ou appartenant à un autre utilisateur (indistinct)

GET /api/v1/statistics 🟢 Livré

Compteurs du dashboard, structurés par catégorie et extensibles.

  • Auth : auth:sanctum
  • Requête : aucune
  • Réponse (200) : json { "claims": { "total": 4, "in_progress": 2, "awaiting_insurer": 1, "closed": 1 }, "settlements": { "received": 1, "amount": 350000, "annual_variation": 0.12 } } Règles : claims.total = in_progress + awaiting_insurer + closed. in_progress = statuts DRAFT + SIGNED + IN_PROGRESS ; awaiting_insurer = SENT ; closed = CLOSED. settlements.amount est en centimes d'euro.
  • Erreurs :
  • 401 — non authentifié

GET /api/v1/properties 🟢 Livré

Liste les biens déjà déclarés par l'utilisateur (dédupliqués par adresse exacte), pour pré-remplir l'adresse d'une nouvelle déclaration.

  • Auth : auth:sanctum
  • Requête : aucune
  • Réponse (200) — structure proposée, à valider : json { "data": [ { "id": "01JZ...", "address": "12 rue des Lilas", "city": "Lyon", "postal_code": "69003" } ] }
  • Erreurs :
  • 401 — non authentifié

POST /api/v1/claims/{id}/copy-property/{property_id} 🟢 Livré

Copie l'adresse d'un bien existant vers le sinistre courant.

  • Auth : auth:sanctum
  • Requête : aucune
  • Réponse (200) — structure proposée : l'objet Claim avec address, city, postal_code renseignés.
  • Erreurs :
  • 401 — non authentifié
  • 404 — sinistre ou bien inconnu

EP-BACK-07 — API Tronc commun DDE

🟢 Tout cet EP est livré — déployé sur le staging (EP-BACK-07). Le contrat ci-dessous reflète le code réel ; la source de vérité à jour reste /api/docs en ligne.

Persistance des réponses Q1→Q6 du tronc commun (dégât des eaux). Sauf mention contraire, toutes ces routes sont sous auth.dual, sont idempotentes, et retournent l'objet Claim mis à jour.

PATCH /api/v1/claims/{id}/common-trunk/dwelling-type 🟢 Livré

Q1 — Type de bien.

  • Requête : dwelling_type (enum, requis) ∈ HOUSE, APARTMENT, COMMERCIAL_PREMISES, OFFICE, OTHER
  • Réponse (200) : objet Claim avec dwelling_type
  • Erreurs : 401, 404, 422 (enum invalide)

PATCH /api/v1/claims/{id}/common-trunk/address 🟢 Livré

Q2 — Adresse. Restreinte à la France métropolitaine et la Corse (DOM-TOM exclus).

  • Requête :
Champ Type Requis Notes
address string oui adresse complète
city string oui
postal_code string oui codes 01xxx95xxx + 20xxx (Corse) acceptés ; 97xxx/98xxx rejetés
  • Réponse (200) : objet Claim avec address, city, postal_code
  • Erreurs : 401, 404, 422 (code postal hors zone, message explicite)

PATCH /api/v1/claims/{id}/common-trunk/declarant-role 🟢 Livré

Q3 — Qualité du déclarant. Bascule le sous-parcours suivant (statut occupant).

  • Requête : declarant_role (enum, requis) ∈ OWNER, TENANT
  • Réponse (200) : objet Claim avec declarant_role
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/common-trunk/injuries 🟢 Livré

Q4 — Blessures, avec sous-formulaire conditionnel si injuries = YES.

  • Requête :
Champ Type Requis Notes
injuries string enum oui YES, NO, DONT_KNOW
injury_type string requis si YES
injury_severity string requis si YES
injured_party string enum requis si YES SELF, THIRD_PARTY
injured_contact objet contact optionnel si injured_party = THIRD_PARTY — crée un Contact rôle INJURED_THIRD_PARTY
  • Réponse (200) : objet Claim avec les champs blessures
  • Erreurs : 401, 404, 422 (YES sans injury_type / injury_severity)

PATCH /api/v1/claims/{id}/common-trunk/observation-date 🟢 Livré

Q5 — Date de constatation.

  • Requête : observation_date (date ISO YYYY-MM-DD, requis, doit être ≤ aujourd'hui)
  • Réponse (200) : objet Claim avec observation_date
  • Erreurs : 401, 404, 422 (date future)

POST /api/v1/claims/{id}/rooms 🟢 Livré

Q6 — Ajoute une pièce sinistrée.

  • Requête :
Champ Type Requis
name string oui
position integer optionnel
  • Réponse (201) — structure proposée : json { "id": "01JZ...", "claim_id": "01JZ...", "name": "Salon", "position": 1 }
  • Erreurs : 401, 404, 422

DELETE /api/v1/rooms/{id} 🟢 Livré

Supprime une pièce (cascade : supprime parties touchées et dommages associés).

  • Requête : aucune
  • Réponse : 204 No Content
  • Erreurs : 401, 404

POST /api/v1/rooms/{id}/impact-areas 🟢 Livré

Q6 bis — Crée une partie touchée dans une pièce.

  • Requête :
Champ Type Requis Notes
type string enum oui ImpactAreaType : WALL, FLOOR, CEILING, ELECTRICAL_APPLIANCE, OTHER
position integer optionnel
  • Réponse (201) — structure proposée : json { "id": "01JZ...", "room_id": "01JZ...", "type": "WALL", "position": 1 }
  • Erreurs : 401, 404, 422

DELETE /api/v1/impact-areas/{id} 🟢 Livré

Supprime une partie touchée (cascade sur ses dommages).

  • Réponse : 204 No Content
  • Erreurs : 401, 404

POST /api/v1/impact-areas/{id}/damages 🟢 Livré

Q6 bis — Crée un dommage. Le numéro (number, « Dommage n°X ») est auto-incrémenté.

  • Requête :
Champ Type Requis Notes
zone string enum oui INTERIOR, EXTERIOR
description string optionnel
wall_length_m decimal optionnel
wall_width_m decimal optionnel
zone_length_m decimal optionnel
zone_width_m decimal optionnel
  • Réponse (201) — structure proposée : json { "id": "01JZ...", "impact_area_id": "01JZ...", "number": 1, "zone": "INTERIOR", "description": "Auréole plafond", "wall_length_m": 3.20, "wall_width_m": 2.50, "impacted_surface_m2": 8.00 }
  • Erreurs : 401, 404, 422

DELETE /api/v1/damages/{id} 🟢 Livré

Supprime un dommage.

  • Réponse : 204 No Content
  • Erreurs : 401, 404

POST /api/v1/claims/{id}/common-trunk/validate 🟢 Livré

Vérifie la complétude du tronc commun (Q1–Q6 répondus, au moins 1 pièce avec ≥ 1 photo OU LIDAR) puis fait basculer le sinistre vers l'étape « Cause » (origine OWN_PROPERTY uniquement).

  • Requête : aucune
  • Réponse (200) : json { "id": "01JZ...", "status": "PENDING_QUALIFICATION_CAUSE", "next_step": "CAUSE" }
  • Erreurs :
  • 401, 404
  • 422 — tronc incomplet, avec errors listant les manques (réponses manquantes, aucune photo/LIDAR)

EP-BACK-08 — API Statut occupant

🟡 Tout cet EP est À valider. Sous-parcours propriétaire (3 cas) et locataire. Routes sous auth.dual, idempotentes.

PATCH /api/v1/claims/{id}/owner/situation 🟡 À valider

  • Requête : owner_situation (enum, requis) ∈ PRIMARY_RESIDENCE, SECONDARY_RESIDENCE_NOT_RENTED, RENTAL_RESIDENCE
  • Réponse (200) : objet Claim avec owner_situation
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/owner/rental/residence-type 🟡 À valider

  • Requête : residence_type (enum, requis) ∈ APARTMENT, INVESTMENT_BUILDING
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/owner/rental/rental-type 🟡 À valider

  • Requête :
Champ Type Requis Notes
rental_type string enum oui SHORT_TERM, LONG_TERM
rental_start date optionnel
rental_end date optionnel
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/owner/rental/platform 🟡 À valider

  • Requête :
Champ Type Requis Notes
uses_platform boolean oui
platform_name string enum requis si uses_platform = true AIRBNB, BOOKING, OTHER
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422 (uses_platform = true sans platform_name)

PATCH /api/v1/claims/{id}/owner/rental/current-occupant 🟡 À valider

  • Requête :
Champ Type Requis Notes
has_current_occupant boolean oui
occupant objet contact requis si has_current_occupant = true crée un Contact rôle TENANT_OCCUPANT_AT_CLAIM_TIME
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/lease-termination 🟡 À valider

Sous-formulaire résiliation de bail.

  • Requête : sous-objet lease_termination :
Champ Type Notes
requested boolean si false, les sous-champs sont nullifiés
notice_in_progress boolean requis si requested = true
departure_done boolean requis si requested = true
claim_related boolean requis si requested = true
departure_date date optionnel
reason string enum UNINHABITABLE_HOUSING, CONFLICT, OTHER
  • Réponse (200) : objet Claim avec le sous-objet lease_termination
  • Erreurs : 401, 404, 422 (requested = true sans détails)

PATCH /api/v1/claims/{id}/vacancy-duration 🟡 À valider

  • Requête : vacancy_start (date, requis), vacancy_end (date, requis, ≥ vacancy_start)
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422 (plage incohérente)

PUT /api/v1/claims/{id}/contacts/declarant-tenant 🟡 À valider

Coordonnées du locataire déclarant.

  • Requête : first_name, last_name, email, phone (tous requis)
  • Réponse (200) — structure proposée : objet Contact rôle TENANT
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/contacts/owner 🟡 À valider

Coordonnées du propriétaire (consentement RGPD obligatoire). À la validation, déclenche la notification SMS/email au propriétaire.

  • Requête :
Champ Type Requis Notes
first_name string oui
last_name string oui
email string email oui
phone string oui format E.164 attendu
gdpr_consent boolean oui doit valoir true
  • Réponse (200) — structure proposée : objet Contact rôle OWNER
  • Erreurs : 401, 404, 422 (gdpr_consenttrue)

PUT /api/v1/claims/{id}/contacts/agency 🟡 À valider

Coordonnées de l'agence / gestionnaire (consentement RGPD obligatoire).

  • Requête : company_name, first_name, last_name, email, phone, gdpr_consent (tous requis, gdpr_consent = true)
  • Réponse (200) — structure proposée : objet Contact rôle AGENCY
  • Erreurs : 401, 404, 422

EP-BACK-09 — API Branche Chez moi : Cause + Travaux

🟡 Tout cet EP est À valider. Routes sous auth.dual, idempotentes.

PATCH /api/v1/claims/{id}/cause 🟡 À valider

Cause du sinistre — bascule le sous-flow.

  • Requête : cause (enum ClaimCause, requis) ∈ CONSTRUCTION_WORK, NATURAL_EVENT, NONE
  • Réponse (200) : json { "id": "01JZ...", "cause": "CONSTRUCTION_WORK", "status": "PENDING_QUALIFICATION_CONSTRUCTION" } Le status devient PENDING_QUALIFICATION_CONSTRUCTION / PENDING_QUALIFICATION_PHENOMENON / PENDING_QUALIFICATION_OTHER selon la cause.
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/construction/nature 🟡 À valider

Nature des travaux (modal 10 catégories + DONT_KNOW = 11 valeurs).

  • Requête : construction_nature (enum ConstructionNature, 11 valeurs, requis)
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

Liste exacte des 11 valeurs ConstructionNature non détaillée dans le backlog — structure inventée à confirmer côté PO.

PATCH /api/v1/claims/{id}/construction/construction-date 🟡 À valider

Date du chantier — calcule automatiquement la sous-branche de routage.

  • Requête :
Champ Type Requis Notes
reception_date date l'un des deux date de réception du chantier
delivery_date date l'un des deux fallback si pas de réception
not_handed_over boolean optionnel si true → branche NOT_HANDED_OVER
  • Réponse (200) : json { "id": "01JZ...", "construction_branch": "GPA" } construction_branch calculé : < 1 anGPA ; 1 an ≤ delta < 10 ansOVER_1_YEAR ; ≥ 10 ansOVER_10_YEARS ; date inconnue → UNKNOWN_DATE ; not_handed_overNOT_HANDED_OVER.
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/construction/gpa/management 🟡 À valider

Mode de gestion du chantier (branche GPA).

  • Requête : construction_management_mode (enum, requis) ∈ SELF_MANAGED, VIA_BUILDER
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

POST /api/v1/claims/{id}/contacts/construction-company 🟡 À valider

Ajoute une entreprise de travaux (1 ou plusieurs).

  • Requête : company_name, phone, email (requis)
  • Réponse (201) — structure proposée : objet Contact rôle CONSTRUCTION_COMPANY
  • Erreurs : 401, 404, 422

DELETE /api/v1/contacts/{id} 🟡 À valider

Supprime un contact entreprise.

Le chemin de suppression n'est pas figé dans le backlog — route inventée (alternative : DELETE /api/v1/claims/{id}/contacts/{contact_id}). À confirmer.

  • Réponse : 204 No Content
  • Erreurs : 401, 404

PUT /api/v1/claims/{id}/contacts/builder 🟡 À valider

Coordonnées du promoteur / constructeur (un seul).

  • Requête : company_name, phone, email (requis)
  • Réponse (200) — structure proposée : objet Contact rôle BUILDER_DEVELOPER
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/contacts/responsible-company 🟡 À valider

Branche +10 ans — entreprise responsable + n° de police constructeur.

  • Requête : company_name, phone, email, policy_number (requis)
  • Réponse (200) — structure proposée : objet Contact
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/insurance-policies/rc-decennale 🟡 À valider

Assureur RC Pro / Décennale.

  • Requête :
Champ Type Requis
insurer_name string oui
contact_name string optionnel
policy_number string optionnel
email string email optionnel
phone string optionnel
address_line string optionnel
  • Réponse (200) — structure proposée : objet InsurancePolicy type TEN_YEAR_WARRANTY ou PROFESSIONAL_LIABILITY
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/insurance-policies/home-insurance 🟡 À valider

Assureur multirisques de l'utilisateur. Endpoint factorisé (voir aussi EP-10.06) — le type de contrat est dérivé du declarant_role + owner_situation : HOME_INSURANCE_OCCUPANT / HOME_INSURANCE_NON_OCCUPANT (PNO) / multirisques locataire. Si le déclarant est locataire, déclenche la notification au propriétaire.

  • Requête : mêmes champs que rc-decennale (insurer_name requis, le reste optionnel)
  • Réponse (200) — structure proposée : objet InsurancePolicy
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/construction/knows-liability-insurance 🟡 À valider

Branche +10 ans — l'utilisateur connaît-il les coordonnées RC.

  • Requête : knows_liability_insurance (boolean, requis)
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

GET /api/v1/insurers 🟡 À valider

Catalogue d'assureurs (recherche pour le modal de saisie d'assurance).

  • Auth : auth:sanctum (l'EP-01 fige auth:sanctum comme régime par défaut hors funnel ; à confirmer — pourrait être auth.dual ou public)
  • Requête :
Param Type Notes
q string recherche par préfixe, insensible à la casse
page, per_page integer pagination standard
  • Réponse (200) — structure proposée : json { "data": [ { "id": "01JZ...", "name": "AXA France" } ], "meta": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 }, "links": { "first": "...", "last": "...", "prev": null, "next": null } }
  • Erreurs : 401 (si authentifié)

EP-BACK-10 — API Phénomène naturel

🟡 Tout cet EP est À valider. Qualification phénomène naturel + vérification d'arrêté CatNat via georisques.gouv.fr.

PATCH /api/v1/claims/{id}/phenomenon/type 🟡 À valider

  • Auth : auth.dual
  • Requête : phenomenon_type (enum, 7 valeurs, requis) ∈ RIVER_OVERFLOW, GROUNDWATER, STORM, MASSIVE_WATER, HAIL, LANDSLIDE, OTHER
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/phenomenon/prefectoral-decree 🟡 À valider

Arrêté préfectoral CatNat. Si « Oui » ou « Je ne sais pas » sans date → déclenche un job de vérification automatique georisques.

  • Auth : auth.dual
  • Requête :
Champ Type Requis Notes
cat_nat_decree string enum oui YES, NO, DONT_KNOW
decree_publication_date date optionnel
  • Réponse (200) : json { "id": "01JZ...", "cat_nat_decree": "YES", "decree_verification_status": "FOUND", "decree_publication_date": "2026-04-02" } decree_verification_statusPENDING (job en cours), FOUND, NOT_FOUND, ERROR. YES + date → FOUND directement ; YES sans date ou DONT_KNOWPENDING (job déclenché) ; NO → status null.
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/insurance-policies/home-insurance 🟡 À valider

Coordonnées multirisques selon le statut occupant (3 variantes : occupant / PNO / locataire). Même endpoint que EP-09 — voir sa fiche. Si déclarant locataire, déclenche la notification au propriétaire.

Note — champ calculé cat_nat_days_remaining

Quand un arrêté est publié, le détail du sinistre (GET /api/v1/claims/{id}) expose un champ calculé cat_nat_days_remaining = 10 - (aujourd'hui - decree_publication_date), pour afficher le compte à rebours du délai légal de 10 jours.


EP-BACK-11 — API Cause = Aucune des deux

🟡 À valider. Cas DDE sans travaux ni phénomène naturel.

PATCH /api/v1/claims/{id}/no-cause/claim-type 🟡 À valider

Type de sinistre — réutilise l'enum ConstructionNature (11 valeurs) de l'EP-09.

  • Auth : auth.dual
  • Requête : claim_type_detail (enum ConstructionNature, 11 valeurs, requis)
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

Après cet endpoint, la branche NONE réutilise directement PUT /api/v1/claims/{id}/insurance-policies/home-insurance (EP-10.06) pour les coordonnées multirisques. Aucun endpoint spécifique supplémentaire.


EP-BACK-12 — Routage Dommages-Ouvrage

🟡 Tout cet EP est À valider. Logique DO (4 cas + 7 personae). Routes sous auth.dual.

PATCH /api/v1/claims/{id}/do/subscription 🟡 À valider

DO souscrite. DO_NO ou DO_DONT_KNOW → bascule directement sur le cas NO_DO.

  • Requête : do_subscription (enum, requis) ∈ DO_YES, DO_NO, DO_DONT_KNOW
  • Réponse (200) : objet Claim avec do_subscription
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/do/included-areas 🟡 À valider

Endroits intégrés à la DO — calcule construction_damage_case.

  • Requête : included_areas (enum, requis) ∈ ALL_YES, ALL_NO, DONT_KNOW, SOME
  • Réponse (200) : json { "id": "01JZ...", "construction_damage_case": "FULLY_INCLUDED" } Mapping : ALL_YESFULLY_INCLUDED · ALL_NO/DONT_KNOWNOT_INCLUDED · SOMEPARTIAL (le 4ᵉ cas NO_DO vient de do/subscription).
  • Erreurs : 401, 404, 422

PATCH /api/v1/claims/{id}/do/formal-notice-already-sent 🟡 À valider

Mise en demeure déjà envoyée. Si « Oui », l'utilisateur peut joindre le courrier via l'upload de média (type FORMAL_NOTICE_LETTER).

  • Requête : formal_notice_already_sent (boolean, requis)
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/insurance-policies/do 🟡 À valider

Coordonnées de l'assurance Dommages-Ouvrage.

  • Requête : mêmes champs que insurance-policies/rc-decennale (insurer_name requis, reste optionnel)
  • Réponse (200) — structure proposée : objet InsurancePolicy type CONSTRUCTION_DAMAGE_INSURANCE
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/do/partial-qualification 🟡 À valider

Cas PARTIAL — qualification zone par zone. Génère 2 envois distincts (DO + multirisques).

  • Requête :
Champ Type Requis Notes
qualifications array d'objets oui une entrée par dommage : { damage_id, status } avec statusINCLUDED_IN_DO, NOT_INCLUDED. Tous les dommages doivent être qualifiés.
  • Réponse (200) — structure proposée : objet Claim + liste des dispatches créés
  • Erreurs : 401, 404, 422 (toutes les zones ne sont pas qualifiées)

Les services de routage PersonaResolver (calcul du persona A→G) et SequentialDispatchService (logique séquentielle / parallèle d'envoi) sont internes et n'exposent pas d'endpoint. Le persona calculé devrait apparaître dans le détail du sinistre — champ persona à confirmer côté PO.


EP-BACK-13 — Abonnement Stripe

🟡 Tout cet EP est À valider. Modèle freemium déclenché par le persona F (cas PARTIAL, 2ᵉ déclaration).

POST /api/v1/claims/{id}/partial-subscription/checkout 🟡 À valider

Crée une session Stripe Checkout pour l'abonnement Premium. Gating : persona F détecté ET ≥ 1 sinistre déjà validé.

  • Auth : auth:sanctum
  • Requête : aucune (ou success_url / cancel_url optionnels — à confirmer)
  • Réponse (200) — structure proposée : json { "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...", "session_id": "cs_test_..." }
  • Erreurs :
  • 401 — non authentifié
  • 403 — persona ≠ F, ou pas de sinistre antérieur
  • 404 — sinistre inconnu

POST /api/v1/webhooks/stripe 🟡 À valider

Webhook Stripe (events customer.subscription.*, invoice.payment_*). Met à jour la table subscriptions.

  • Auth : public, signature Stripe vérifiée
  • Requête : payload Stripe brut + header Stripe-Signature
  • Réponse (200) : { "received": true }
  • Erreurs :
  • 400 — signature invalide

Gating sur la qualification PARTIAL

PUT /api/v1/claims/{id}/do/partial-qualification (EP-12) est protégé par le middleware EnsurePremiumForPartialDeclaration. Si l'utilisateur n'a pas d'abonnement actif et que c'est sa 2ᵉ déclaration : - 402 Payment Required — le front doit alors lancer le tunnel d'abonnement.


EP-BACK-14 — API Branches Tiers↔moi

🟡 Tout cet EP est À valider. Sinistre causé par un tiers, ou causé à un tiers. Routes sous auth.dual.

PATCH /api/v1/claims/{id}/third-party/type 🟡 À valider

  • Requête : third_party_type (enum, requis) ∈ NEIGHBOR, BUILDING_MANAGER, APPOINTED_COMPANY
  • Réponse (200) : objet Claim
  • Erreurs : 401, 404, 422

PUT /api/v1/claims/{id}/contacts/third-party 🟡 À valider

Coordonnées du tiers — champs adaptés au type.

  • Requête :
  • NEIGHBOR / APPOINTED_COMPANY : first_name, last_name, email, address_line, phone
  • BUILDING_MANAGER : company_name (nom du syndic), first_name, last_name, email, address_line, phone
  • Réponse (200) — structure proposée : objet Contact rôle THIRD_PARTY
  • Erreurs : 401, 404, 422 (champs incohérents avec le type)

PUT /api/v1/claims/{id}/insurance-policies/third-party-insurer 🟡 À valider

Assureur du tiers — facultatif (peut être vide).

  • Requête : mêmes champs que insurance-policies/home-insurance, tous optionnels
  • Réponse (200) — structure proposée : objet InsurancePolicy type HOME_INSURANCE_THIRD_PARTY
  • Erreurs : 401, 404

PUT /api/v1/claims/{id}/contacts/third-party-victim 🟡 À valider

Branche Moi → tiers — coordonnées du tiers victime. La finalisation déclenche un envoi LRE obligatoire au tiers victime + double déclaration multirisques (+ DO si do_subscription = DO_YES).

  • Requête : first_name, last_name, email, address_line, phone (requis)
  • Réponse (200) — structure proposée : objet Contact rôle THIRD_PARTY_VICTIM
  • Erreurs : 401, 404, 422

Les services d'envoi (Tiers→moi = 1 envoi multirisques ; Moi→tiers = LRE + multirisques + DO) sont internes — pas d'endpoint dédié. Les envois sont consultables via GET /api/v1/claims/{id}/dispatches (EP-19).


EP-BACK-15 — Mise en demeure entreprise

🟢 Livré. Génération + signature + envoi de la MED entreprise. 4 routes finales sous auth.dual. Modèle FormalNoticeDraft pré-rempli à partir des sections du tronc commun + branche, PDF Blade généré via Spatie\LaravelPdf, signature canvas dédiée (distincte du constat), envoi via AR24 (LRE).

Routes finales (renommage 2026-06-29) : les routes historiques /med/generate, /med/{med_id}/sign, /formal-notice/{id}/send ont été remplacées par les 5 routes ci-dessous, structurées par étape du parcours (draft → pdf → sign → dispatch).

GET /api/v1/claims/{id}/formal-notice/draft 🟢 Livré

Lit le FormalNoticeDraft courant du claim. Pré-rempli côté backend depuis les rooms / damages / contacts / coordonnées entreprise au premier appel.

  • Requête : aucune
  • Réponse (200) : payload FormalNoticeDraft (cf. PUT ci-dessous pour la structure).
  • Erreurs : 401, 404, 422 (aucune entreprise renseignée sur le claim)

PUT /api/v1/claims/{id}/formal-notice/draft 🟢 Livré

Met à jour le FormalNoticeDraft (récap éditable section par section). Idempotent.

  • Requête : sous-objets optionnels recipient, identification, evidence pour override section par section. Aucun champ requis (régénération depuis l'état claim).
  • Réponse (200) : json { "id": "01JZ...", "claim_id": "01JZ...", "recipient": { "company_name": "BTP Construction SARL", "address_line": "12 rue du Chantier", "city": "Lyon", "postal_code": "69003" }, "identification": { "summary": "Infiltrations …", "damages": [{ "room": "Salon", "type": "WALL", "description": "Auréole plafond" }] }, "evidence": { "media_ids": ["01JZ…", "01JZ…"] }, "updated_at": "2026-06-29T10:00:00+00:00" }
  • Erreurs : 401, 404, 422 (draft verrouillé après signature)

GET /api/v1/claims/{id}/formal-notice/pdf 🟢 Livré

Génère / récupère le PDF aperçu de la MED (PDF Blade + Spatie\LaravelPdf, signature non encore embarquée). Régénérable tant que non signé.

  • Requête : aucune
  • Réponse (200) : json { "signed_url": "https://storage.googleapis.com/…/formal-notice-v1.pdf?X-Goog-Signature=…", "expires_at": "2026-06-29T11:00:00+00:00", "version": 1, "signed_at": null }
  • Erreurs : 401, 404, 422 (draft incomplet)

POST /api/v1/claims/{id}/formal-notice/sign 🟢 Livré

Signature canvas dédiée. Embarque la signature dans le PDF, met à jour signed_at + hash_sha256 + verrouille le draft.

  • Requête : signature (string, image PNG en base64, requis)
  • Réponse (200) : json { "id": "01JZ…", "version": 1, "signed_at": "2026-06-29T11:05:00+00:00", "hash_sha256": "e3b0c4…", "signed_url": "https://…/formal-notice-v1-signed.pdf?…" }
  • Erreurs : 401, 404, 422 (MED déjà signée, ou PDF non encore généré)

POST /api/v1/claims/{id}/formal-notice/dispatch/payment-intent 🟢 Livré

Crée un PaymentIntent (Stripe — frais AR24 6,00 €) et prépare le Dispatch LRE associé. À la confirmation Stripe, l'envoi AR24 est déclenché et le dispatch passe à SENT avec legal_deadline_days = 8. À J+8, le cron DetectUnsuccessfulFormalNoticeJob (Laravel Scheduler, daily 09:00) scanne les FormalNotice au statut SENT deadline expirée, pose data->med_overdue_triggered_at sur le dispatch parent et déclenche un push best-effort.

  • Requête : aucune
  • Réponse (200) : json { "payment_intent_client_secret": "pi_…_secret_…", "amount_cents": 600, "currency": "EUR", "dispatch_id": "01JZ…" }
  • Erreurs : 401, 404, 422 (MED non signée)

EP-BACK-16 — Génération constat amiable + rapport

🟡 À valider. 2 documents PDF de fin de parcours. Routes sous auth.dual.

POST /api/v1/claims/{id}/documents/amicable-report 🟡 À valider

Génère le constat amiable. Régénérable tant que non signé (incrémente la version).

  • Requête : aucune
  • Réponse (201) — structure proposée : json { "id": "01JZ...", "type": "AMICABLE_REPORT", "version": 1, "signed_at": null, "signed_url": "https://storage.googleapis.com/.../amicable-report-v1.pdf?X-Goog-Signature=..." }
  • Erreurs : 401, 404, 422 (document signé → régénération refusée)

POST /api/v1/claims/{id}/documents/report 🟡 À valider

Génère le rapport approfondi (annexe technique). Mêmes règles que le constat.

  • Requête : aucune
  • Réponse (201) — structure proposée : objet Document type DETAILED_REPORT
  • Erreurs : 401, 404, 422

EP-BACK-17 — Signature électronique

🟢 Livré (suivi adaptatif inclus). Signature locale par canvas embarquée dans le PDF (horodatage + hash SHA-256). Service mutualisé entre constat amiable et MED (cf. EP-BACK-15). Le « suivi adaptatif des envois » prévu dans cette épique est livré sous forme de l'endpoint dédié GET /api/v1/claims/{id}/dispatch-tracking (cf. EP-BACK-19).

POST /api/v1/claims/{id}/documents/{doc_id}/sign 🟢 Livré

Signe un document (constat amiable). Embarque l'image de signature dans le PDF, met à jour signed_at, signature_path et hash_sha256.

  • Auth : auth.dual
  • Requête : signature (string, image PNG en base64, requis)
  • Réponse (200) — structure proposée : json { "id": "01JZ...", "type": "AMICABLE_REPORT", "version": 1, "signed_at": "2026-05-18T11:00:00+00:00", "hash_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
  • Erreurs : 401, 404 (document inexistant), 422 (document déjà signé)

EP-BACK-18 — Analyse IA et estimation

🟡 À valider. Estimation des dommages et identification des garanties via prestataire IA. Routes sous auth.dual.

POST /api/v1/claims/{id}/ai-analysis 🟡 À valider

Démarre un job d'analyse asynchrone. Retourne immédiatement en statut PENDING ; la progression est diffusée via Pusher (events analysis-progress, analysis-completed).

  • Requête : aucune
  • Réponse (202) — structure proposée : json { "status": "PENDING", "claim_id": "01JZ..." }
  • Erreurs : 401, 404

GET /api/v1/claims/{id}/ai-analysis/results 🟡 À valider

Récupère les résultats de l'analyse.

  • Requête : aucune
  • Réponse (200) : json { "status": "COMPLETED", "estimated_damage_eur": 4250.00, "activated_guarantees": ["WATER_DAMAGE_GUARANTEE", "FURNITURE_GUARANTEE"], "uncovered_guarantees": [], "confidence": 0.85 } statusPENDING, COMPLETED, OBSOLETE.
  • Erreurs :
  • 401
  • 404 — analyse jamais lancée pour ce sinistre

POST /api/v1/claims/{id}/edit-after-ia 🟡 À valider

Permet de modifier la déclaration après l'analyse. Repasse le sinistre au statut IN_COMMON_TRUNK et marque l'analyse précédente OBSOLETE.

  • Requête : aucune
  • Réponse (200) — structure proposée : json { "id": "01JZ...", "status": "IN_COMMON_TRUNK" }
  • Erreurs : 401, 404

EP-BACK-19 — Envoi LRE via AR24

🟡 Tout cet EP est À valider. Envoi de lettres recommandées électroniques via AR24.

PATCH /api/v1/claims/{id}/dispatches/{dispatch_id}/mode 🟡 À valider

Choix du mode d'envoi.

  • Auth : auth.dual
  • Requête : mode (enum, requis) ∈ EMAIL, LRE_AR24
  • Réponse (200) — structure proposée : objet Dispatch avec mode
  • Erreurs : 401, 404, 422

POST /api/v1/claims/{id}/dispatches/{dispatch_id}/pay 🟡 À valider

Tunnel de paiement AR24 (6,00 € par envoi LRE — tarif à confirmer).

  • Auth : auth.dual
  • Requête : aucune
  • Réponse (200) — structure proposée : json { "payment_url": "https://...", "amount_cents": 600, "currency": "EUR" }
  • Erreurs : 401, 404

POST /api/v1/claims/{id}/dispatches/{dispatch_id}/send 🟡 À valider

Déclenche l'envoi LRE via AR24 avec les pièces jointes (constat + rapport, ou MED).

  • Auth : auth.dual
  • Requête : aucune
  • Réponse (200) — structure proposée : json { "id": "01JZ...", "status": "SENT", "ar24_tracking_number": "AR24-2026-XXXXXX", "sent_at": "2026-05-18T12:00:00+00:00" }
  • Erreurs : 401, 404, 422 (envoi non payé)

POST /api/v1/webhooks/ar24 🟡 À valider

Webhook AR24 (events distribuée / accusé reçu / échec). Met à jour les statuts et notifie l'utilisateur (email + push + Pusher).

  • Auth : public, signature AR24 vérifiée
  • Requête : payload AR24 brut
  • Réponse (200) : { "received": true }
  • Erreurs :
  • 401 — signature invalide

GET /api/v1/claims/{id}/dispatches 🟡 À valider

Timeline des envois du sinistre : statut courant + historique + prochain délai.

  • Auth : auth.dual
  • Requête : aucune
  • Réponse (200) — structure proposée : json { "data": [ { "id": "01JZ...", "mode": "REGISTERED_LETTER", "status": "DELIVERED", "ar24_tracking_number": "AR24-2026-XXXXXX", "legal_deadline_days": 8, "days_remaining": 5, "is_overdue": false, "created_at": "2026-05-18T12:00:00+00:00", "sent_at": "2026-05-18T12:05:00+00:00", "delivered_at": "2026-05-19T09:00:00+00:00", "acknowledged_at": null } ] }
  • Erreurs : 401, 404

GET /api/v1/claims/{id}/dispatch-tracking 🟢 Livré

Suivi adaptatif backend-driven — endpoint dédié consommé par l'écran ClaimDispatchTrackingScreen côté mobile. Le contrôleur ShowClaimDispatchTrackingController délègue toute la logique métier au service App\Services\Claim\ClaimDispatchScenarioResolver, qui croise la branche d'envoi du claim et l'état AR24 / cron / acknowledgments pour produire le payload de rendu. Le mobile rend purement le payload — zéro templating local.

  • Auth : auth.dual
  • Requête : aucune
  • Réponse (200) — payload unique (pas de wrapper data) déjà rendu par le resolver, le mobile l'affiche tel quel : json { "claim_id": "01kvqf6p6v34rgrce4a7cc5wah", "scenario_key": "works_gpa", "phase_key": "awaiting_med", "enabled": true, "icon_kind": "orange_envelope", "banner_tone": "orange", "title": "Sans réponse de l'entreprise sous 8 jours, vous recevrez une notification pour déclarer le sinistre à votre assureur Dommages-Ouvrage", "info_banner": "En cas de silence, la mise en demeure permet de déclencher la garantie de parfait achèvement auprès de votre assureur Dommages-Ouvrage.", "cta_label": "Terminer", "action": null, "recipients": [ { "kind": "formal_notice", "name": "Bâti Pro SARL", "card_state": "med_sent", "head": "Bâti Pro SARL — Mise en demeure envoyée", "subline": "Envoyée le 22 juin 2026 · Réponse attendue avant le 30 juin 2026", "dispatch_id": "01kvqfa1xx12345abcdefghijk", "status": "SENT", "tracking_number": "FAKE-AR24-A04E4EF6", "sent_at": "2026-06-22T08:30:00+00:00", "delivered_at": null, "acknowledged_at": null, "deadline": "2026-06-30T08:30:00+00:00", "deadline_days": 8, "deadline_kind": "calendar_days" } ] }

Exemple avec action escalate_dispatch (phase works_do_full.do_overdue, le dispatch_id est celui du DO parent à escalader) : json "action": { "kind": "escalate_dispatch", "label": "Déclarer à mon assureur multirisques", "dispatch_id": "01kw9ss27djyv66rjd67m0r5f5" }

Exemple avec action dispatch_do (phase works_gpa.med_overdue, dispatch_id à null puisqu'on crée le premier DO) : json "action": { "kind": "dispatch_do", "label": "Déclarer à mon assureur Dommages-Ouvrage", "dispatch_id": null }

  • Énumérations :
  • scenario_keynatural_phenomenon, none_cause, works_no_do, works_do_full, works_do_partial, works_do_none, works_gpa, neighbor (8 valeurs). Si aucune branche ne matche, le resolver renvoie scenario_key: "fallback" avec enabled: false et le wording neutre.
  • phase_keydefault, awaiting_med, med_overdue, awaiting_do, do_overdue, escalated, do_acknowledged, declarant_only, with_third_party
  • icon_kindblue_envelope, orange_envelope, orange_warning, green_check
  • banner_toneblue, orange, green
  • action.kinddispatch_do, dispatch_multi, escalate_dispatch (la clé est bien kind, pas type). escalate_dispatch porte toujours dispatch_id = ULID du DO parent à escalader. Les autres ont dispatch_id: null.
  • recipients[].kindmultirisques, damage_ouvrage, formal_notice, third_party
  • recipients[].card_statecreated, sent_pending, sent_no_response, delivered, acknowledged, auto_dispatched_lre, auto_dispatched_email, failed, med_sent, med_no_response (10 valeurs émises par ClaimDispatchScenarioResolver::resolveCardState()). Les deux états auto_dispatched_lre / auto_dispatched_email permettent au mobile de différencier le rendu selon le canal (timeline AR24 vs simple confirmation email).

Matrice scenario_key × phase_key

Reflet exact de config/claim_dispatch_tracking.php :

scenario_key Phases déclarées Destinataires actifs (par phase) Action contextuelle
natural_phenomenon default multirisques
none_cause default multirisques
works_no_do default multirisques, third_party
works_do_full awaiting_do, do_overdue, escalated, do_acknowledged DO seul, puis DO+multi en escalated escalate_dispatch sur do_overdue
works_do_partial default (scénario désactivé V1 — TRACKING_WORKS_DO_PARTIAL_ENABLED=false) DO + multi
works_do_none default multirisques, third_party
works_gpa awaiting_med, med_overdue, awaiting_do, do_overdue, escalated MED → MED+DO → MED+DO+multi dispatch_do sur med_overdue, escalate_dispatch sur do_overdue
neighbor declarant_only, with_third_party multirisques ± third_party
  • Erreurs : 401, 404

EP-BACK-20bis — Contact-Support

🟢 Livré (remplace EP-BACK-20 VOE Conseil). L'idée d'une mise en relation chat avec un conseiller « VOE » a été abandonnée le 2026-06-29 au profit d'un formulaire libre qui crée un ticket par email côté équipe HomyExpert. Aucune table voe_messages, aucun chat temps réel — un endpoint POST et un Mailable.

POST /api/v1/claims/{claim}/contact-support 🟢 Livré

Crée un ticket support attaché au claim. Envoie un Mail\ClaimSupportRequest (Mailable brandé HomyExpert : logo PNG embed base64, background gris clair, footer vide) à l'équipe HomyExpert.

  • Auth : auth.dual
  • Requête :
Champ Type Requis Notes
subject string oui max 200 caractères
message string oui max 5000 caractères
  • Réponse : 204 No Content
  • Erreurs :
  • 401 — non authentifié
  • 404 — claim inconnu / non accessible
  • 422subject ou message vide ou trop long

Migration de renommage 2026-06-29 : la step FINALIZATION_VOE_CHAT a été renommée FINALIZATION_CONTACT_SUPPORT (migration data réelle 2026_06_29_151517 qui réécrit claims.current_step et claim_step_histories.step). L'enum FINALIZATION_VOE_CHAT n'existe plus.


EP-BACK-21 — Escalade

🟢 Livré. Parcours d'escalade quand l'entreprise ne répond pas dans les 8 jours après la MED. L'endpoint historique POST /escalation/trigger n'existe pas — l'escalade est exposée comme l'action escalate_dispatch dans le payload de GET /dispatch-tracking (cf. EP-BACK-19), portant le dispatch_id du DO parent (ou du dispatch suivant attendu).

Détection automatique J+8

Le cron DetectUnsuccessfulFormalNoticeJob (Laravel Scheduler, daily 09:00) scanne les FormalNotice au statut SENT dont la deadline AR24 est expirée (8 jours ouvrés). Pour chaque MED infructueuse :

  • pose data->med_overdue_triggered_at sur le dispatch parent
  • déclenche un push best-effort vers le déclarant (canal notifications)
  • bascule la phase du payload dispatch-tracking en med_overdue côté UI mobile

L'utilisateur peut ensuite tapoter le CTA « Envoyer à l'assureur Dommages-Ouvrage » qui consomme l'action escalate_dispatch (création du dispatch DO côté backend, bascule en phase escalated).

La logique fallback multirisques de 3ᵉ niveau suit le même pattern : phase do_overdue → action escalate_dispatch portant le dispatch multirisques attendu.


EP-BACK-22 — Super administration

🟡 À valider. Backoffice embarqué dans le backend Laravel (Inertia + Vue 3). Endpoints réservés au rôle admin. Hors périmètre des fronts mobile et web — listés pour exhaustivité.

GET /api/v1/admin/claims/stats 🟡 À valider

Compteurs du dashboard admin.

  • Auth : auth:sanctum + rôle admin
  • Requête : aucune
  • Réponse (200) — structure proposée : json { "total": 128, "in_progress": 40, "signed": 22, "sent": 30, "closed": 36 }
  • Erreurs :
  • 401 — non authentifié
  • 403 — utilisateur sans rôle admin

GET /api/v1/admin/claims 🟡 À valider

Listing de tous les sinistres (read-only), avec filtres.

  • Auth : auth:sanctum + rôle admin
  • Requête : status, date_from, date_to, user_id (filtres optionnels) + pagination
  • Réponse (200) — structure proposée : enveloppe paginée d'objets Claim
  • Erreurs : 401, 403

EP-BACK-23 — Observabilité et monitoring

GET /api/v1/health 🟢 Livré

Liveness — voir EP-BACK-01.

GET /api/v1/health/deep 🟢 Livré

Readiness — exécute les health checks spatie/laravel-health (Database, Cache, Environment, DebugMode, OptimizedApp, UsedDiskSpace). Le statut global est agrégé.

  • Auth : public, protégé par un token optionnel (header X-Health-Token — accessible sans token si HEALTH_SECRET_TOKEN non configuré)
  • Requête : header X-Health-Token si configuré côté env
  • Réponse (200 si ok/warning, 503 si failed) : json { "status": "ok", "checks": [ { "name": "Database", "status": "ok", "message": "" }, { "name": "Cache", "status": "ok", "message": "" }, { "name": "UsedDiskSpace", "status": "warning", "message": "Disk usage 78%" } ] }
  • Erreurs :
  • 403X-Health-Token manquant ou incorrect quand HEALTH_SECRET_TOKEN est configuré
  • 503 — au moins un check en statut failed

Les métriques Cloud Monitoring (anciennement T-BACK-23.02) sont déplacées en EP-BACK-25 et n'exposent pas d'endpoint API consommable par les fronts.


EP-BACK-24 — Internationalisation

🟡 Transverse — à valider. Pas d'endpoint dédié. Tous les contenus textuels émis par le backend (emails, SMS, push, in-app, PDFs, messages d'erreur API, validations) sont localisés selon la locale de l'utilisateur.

Impact pour les fronts : - La locale est résolue par requête en cascade : utilisateur authentifié (users.locale) > header Accept-Language > fallback fr. - Les fronts devraient envoyer le header Accept-Language sur les requêtes en mode invité pour que les emails / SMS partent dans la bonne langue. - V1 : fr actif uniquement ; l'infrastructure est prête pour en, es, de, it. - Les messages d'erreur de l'enveloppe { message, errors } sont localisés — ne pas les parser, se fier aux codes HTTP et aux clés de errors.


EP-BACK-25 — Provisioning infrastructure

🟡 Hors API. Provisioning GCP, comptes prestataires, Cloud Monitoring. Aucun endpoint exposé aux fronts.


Annexe — endpoints internes (non consommables par les fronts)

Pour information, les endpoints suivants existent mais ne sont pas destinés aux clients mobile / web — ils sont appelés par Cloud Tasks ou les prestataires tiers :

  • POST /internal/jobs/dispatch — handler générique des jobs Cloud Tasks (protégé OIDC Google)
  • POST /internal/jobs/generate-thumbnail — génération de vignettes (protégé OIDC)
  • POST /internal/webhooks/mail/maileroo — webhook bounces / complaints email (signature vérifiée)

Les webhooks POST /api/v1/webhooks/stripe et POST /api/v1/webhooks/ar24 sont sous le préfixe /api/v1 mais relèvent de la même logique : ils sont appelés par Stripe / AR24, pas par les fronts.