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êteX-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/jsonrecommandé 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,
usersexposé 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 requisauth:sanctum— token utilisateur strictauth.dual— accepte un token utilisateur ou un token de session invité- Erreurs : enveloppe constante
{ message, errors? }. Le champerrorsn'apparaît que pour les 422 (validation Laravel). Mapping figé : 401— « Unauthenticated. »403— « Forbidden. »404— « Resource not found. »422— erreur de validation, avecerrorsdétaillé par champ500— « Server Error. »- Pagination : page-based standard Laravel —
?page=N&per_page=N(défautper_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) enprod. - 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 :
422—localenon reconnue429— 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_namemanquant429— 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 :
ulidethashdans l'URL ;expiresetsignatureen 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 invalide429— 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 :
401—identity_tokeninvalide / expiré / mauvaise signature / mauvais issuer / mauvaise audience422—identity_tokenmanquant429— 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 :
401—id_tokeninvalide / expiré / signature / issuer non reconnu / audience non listée422—id_tokenmanquant429— 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:sanctumstrict (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 token404— guest token inconnu / expiré / déjà consommé (indistinct, idempotent)422—guest_tokenmanquant
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 accessible422—typenon autorisé,mimeincohérent,size_bytesau-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 ouupload_tokeninconnu422— 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 sinistre404— 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étaire404— 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é422—tokenouplatformmanquant / 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_step∈INCOMPLETE(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 accessible422— 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 dansmeta: 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ésmedia/documents/dispatchesn'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= statutsDRAFT+SIGNED+IN_PROGRESS;awaiting_insurer=SENT;closed=CLOSED.settlements.amountest 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
Claimavecaddress,city,postal_coderenseigné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
Claimavecdwelling_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 01xxx–95xxx + 20xxx (Corse) acceptés ; 97xxx/98xxx rejetés |
- Réponse (200) : objet
Claimavecaddress,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
Claimavecdeclarant_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
Claimavec les champs blessures - Erreurs :
401,404,422(YESsansinjury_type/injury_severity)
PATCH /api/v1/claims/{id}/common-trunk/observation-date 🟢 Livré¶
Q5 — Date de constatation.
- Requête :
observation_date(date ISOYYYY-MM-DD, requis, doit être ≤ aujourd'hui) - Réponse (200) : objet
Claimavecobservation_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,404422— tronc incomplet, avecerrorslistant 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
Claimavecowner_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 = truesansplatform_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
Claimavec le sous-objetlease_termination - Erreurs :
401,404,422(requested = truesans 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
ContactrôleTENANT - 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
ContactrôleOWNER - Erreurs :
401,404,422(gdpr_consent≠true)
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
ContactrôleAGENCY - 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(enumClaimCause, requis) ∈CONSTRUCTION_WORK,NATURAL_EVENT,NONE - Réponse (200) :
json { "id": "01JZ...", "cause": "CONSTRUCTION_WORK", "status": "PENDING_QUALIFICATION_CONSTRUCTION" }LestatusdevientPENDING_QUALIFICATION_CONSTRUCTION/PENDING_QUALIFICATION_PHENOMENON/PENDING_QUALIFICATION_OTHERselon 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(enumConstructionNature, 11 valeurs, requis) - Réponse (200) : objet
Claim - Erreurs :
401,404,422
Liste exacte des 11 valeurs
ConstructionNaturenon 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_branchcalculé :< 1 an→GPA;1 an ≤ delta < 10 ans→OVER_1_YEAR;≥ 10 ans→OVER_10_YEARS; date inconnue →UNKNOWN_DATE;not_handed_over→NOT_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
ContactrôleCONSTRUCTION_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
ContactrôleBUILDER_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
InsurancePolicytypeTEN_YEAR_WARRANTYouPROFESSIONAL_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_namerequis, 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 figeauth:sanctumcomme régime par défaut hors funnel ; à confirmer — pourrait êtreauth.dualoupublic) - 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_status∈PENDING(job en cours),FOUND,NOT_FOUND,ERROR.YES+ date →FOUNDdirectement ;YESsans date ouDONT_KNOW→PENDING(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(enumConstructionNature, 11 valeurs, requis) - Réponse (200) : objet
Claim - Erreurs :
401,404,422
Après cet endpoint, la branche
NONEréutilise directementPUT /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
Claimavecdo_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_YES→FULLY_INCLUDED·ALL_NO/DONT_KNOW→NOT_INCLUDED·SOME→PARTIAL(le 4ᵉ casNO_DOvient dedo/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_namerequis, reste optionnel) - Réponse (200) — structure proposée : objet
InsurancePolicytypeCONSTRUCTION_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 status ∈ INCLUDED_IN_DO, NOT_INCLUDED. Tous les dommages doivent être qualifiés. |
- Réponse (200) — structure proposée : objet
Claim+ liste desdispatchescréés - Erreurs :
401,404,422(toutes les zones ne sont pas qualifiées)
Les services de routage
PersonaResolver(calcul du persona A→G) etSequentialDispatchService(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 — champpersonaà 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_urloptionnels — à 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érieur404— 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,phoneBUILDING_MANAGER:company_name(nom du syndic),first_name,last_name,email,address_line,phone- Réponse (200) — structure proposée : objet
ContactrôleTHIRD_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
InsurancePolicytypeHOME_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
ContactrôleTHIRD_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 viaGET /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}/sendont é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.PUTci-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,evidencepour 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
DocumenttypeDETAILED_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 }status∈PENDING,COMPLETED,OBSOLETE. - Erreurs :
401404— 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
Dispatchavecmode - 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_key∈natural_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 renvoiescenario_key: "fallback"avecenabled: falseet le wording neutre.phase_key∈default,awaiting_med,med_overdue,awaiting_do,do_overdue,escalated,do_acknowledged,declarant_only,with_third_partyicon_kind∈blue_envelope,orange_envelope,orange_warning,green_checkbanner_tone∈blue,orange,greenaction.kind∈dispatch_do,dispatch_multi,escalate_dispatch(la clé est bienkind, pastype).escalate_dispatchporte toujoursdispatch_id= ULID du DO parent à escalader. Les autres ontdispatch_id: null.recipients[].kind∈multirisques,damage_ouvrage,formal_notice,third_partyrecipients[].card_state∈created,sent_pending,sent_no_response,delivered,acknowledged,auto_dispatched_lre,auto_dispatched_email,failed,med_sent,med_no_response(10 valeurs émises parClaimDispatchScenarioResolver::resolveCardState()). Les deux étatsauto_dispatched_lre/auto_dispatched_emailpermettent 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 accessible422—subjectoumessagevide ou trop long
Migration de renommage 2026-06-29 : la step
FINALIZATION_VOE_CHATa été renomméeFINALIZATION_CONTACT_SUPPORT(migration data réelle2026_06_29_151517qui réécritclaims.current_stepetclaim_step_histories.step). L'enumFINALIZATION_VOE_CHATn'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_atsur le dispatch parent - déclenche un push best-effort vers le déclarant (canal
notifications) - bascule la phase du payload
dispatch-trackingenmed_overduecô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→ actionescalate_dispatchportant 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ôleadmin - 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ôleadmin
GET /api/v1/admin/claims 🟡 À valider¶
Listing de tous les sinistres (read-only), avec filtres.
- Auth :
auth:sanctum+ rôleadmin - 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 siHEALTH_SECRET_TOKENnon configuré) - Requête : header
X-Health-Tokensi configuré côté env - Réponse (200 si
ok/warning, 503 sifailed) :json { "status": "ok", "checks": [ { "name": "Database", "status": "ok", "message": "" }, { "name": "Cache", "status": "ok", "message": "" }, { "name": "UsedDiskSpace", "status": "warning", "message": "Disk usage 78%" } ] } - Erreurs :
403—X-Health-Tokenmanquant ou incorrect quandHEALTH_SECRET_TOKENest configuré503— au moins un check en statutfailed
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.