API Sessions
Une Session correspond à une inspection. Créez-en une, émettez une invitation d'utilisateur terrain, capturez des preuves, terminez-la. Tout le reste se rattache à cette ressource.
L'objet Session
{
"id": "0c8f4d2e-1a3b-4c5d-9e7f-1234567890ab",
"status": "open",
"operator_email": "ops@yourco.com",
"scheduled_for": "2026-05-23T10:00:00Z",
"started_at": "2026-05-23T10:00:14Z",
"ended_at": null,
"notes": "Vehicle damage — claim CL-2026-0042",
"locale": "fr",
"consent_state": { "camera": "granted", "gps": "granted" },
"campaign": null,
"created_at": "2026-05-21T14:21:00Z",
"updated_at": "2026-05-23T10:00:14Z"
} | Statut | Signification |
|---|---|
created | La ligne de session existe ; personne n'a encore rejoint. |
open | L'utilisateur terrain a rejoint ; la session est en direct. |
recording | Enregistrement en cours (optionnel, déclenché par l'opérateur). |
closed | Session terminée. Tête de chaîne ancrée à la TSA ; rapports disponibles. |
expired | Session planifiée que personne n'a rejointe dans le délai (TTL). |
Créer une session
POST /api/v1/public/sessions — Portée sessions:write
curl -X POST https://app.nexbasira.com/api/v1/public/sessions \
-H "Authorization: Bearer nb_sec_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"notes": "Vehicle damage — claim CL-2026-0042",
"scheduled_for": "2026-05-23T10:00:00Z",
"locale": "fr"
}' Renvoie l'objet Session fraîchement créé (HTTP 201).
Champs du corps
| Champ | Type | Requis | Notes |
|---|---|---|---|
notes | string | non | Visible par l'opérateur. Affiché dans les e-mails d'invitation. |
scheduled_for | ISO 8601 | non | Une date future déclenche des e-mails de rappel 24 h + 1 h avant. Omettez pour « démarrer maintenant ». |
locale | string | non | L'une des 14 locales prises en charge. Détermine la langue de la SPA + du rapport PDF. Par défaut, la préférence de l'organisation. |
campaign | UUID | non | FK optionnelle vers une Campaign pour un reporting groupé. |
Lister les sessions
GET /api/v1/public/sessions — Portée sessions:read
curl https://app.nexbasira.com/api/v1/public/sessions?limit=25 \
-H "Authorization: Bearer nb_sec_..." Paginé par curseur. Passez le paramètre cursor en reprenant le next_cursor de la réponse précédente pour paginer.
{
"data": [{ /* Session, Session, ... */ }],
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAy..."
} Récupérer une session
GET /api/v1/public/sessions/{session_id} — Portée sessions:read
Terminer une session
POST /api/v1/public/sessions/{session_id}/end — Portée sessions:write
Ferme la session, déclenche l'ancrage de la tête de chaîne auprès de toutes les TSA configurées, et lance le post-traitement de l'enregistrement si un enregistrement était en cours. Idempotent — un appel sur une session déjà fermée renvoie l'objet Session fermé sans réancrage.
Émettre une invitation d'utilisateur terrain
POST /api/v1/public/sessions/{session_id}/participants — Portée participants:write
curl -X POST https://app.nexbasira.com/api/v1/public/sessions/0c8f.../participants \
-H "Authorization: Bearer nb_sec_..." \
-H "Content-Type: application/json" \
-d '{
"recipient_first_name": "Alex",
"recipient_last_name": "Garcia",
"recipient_email": "alex@policyholder.com",
"send_email": true,
"ttl_minutes": 1440
}' {
"id": "i-1",
"session": "0c8f...",
"role": "field",
"expires_at": "2026-05-24T10:00:00Z",
"recipient_first_name": "Alex",
"recipient_last_name": "Garcia",
"recipient_email": "alex@policyholder.com",
"recipient_phone": "",
"join_url": "https://app.nexbasira.com/join/0c8f.../?t=tok_PLAINTEXT_ONCE",
"otp_code": "487192",
"otp_required": true,
"otp_expires_at": "2026-05-23T10:10:00Z",
"created_at": "2026-05-23T10:00:00Z"
} Le join_url et le otp_code sont affichés une seule fois dans la réponse. Les tokens sont liés à l'IP/UA, à usage unique et limités dans le temps.
Connexion à deux facteurs (OTP)
Lorsque vous fournissez au moins l'un de recipient_email ou recipient_phone, la plateforme émet automatiquement un OTP numérique à 6 chiffres et l'envoie sur le ou les canaux correspondants dans un message distinct de l'URL de connexion — défense en profondeur pour qu'un e-mail ou un SMS transféré ne divulgue pas les deux facteurs à la fois. Le code en clair est aussi renvoyé dans la réponse ( otp_code) afin que vous puissiez le repartager manuellement en cas d'échec de distribution.
Côté terrain, la SPA affiche l'invite OTP à la première utilisation. Soumettez le code via l'en-tête X-Join-OTP lors d'une nouvelle tentative de GET /v1/sessions/{id}/join/{token} — l'en-tête (et non l'URL) garde le code hors de l'historique du navigateur et des logs d'accès.
Règles OTP :
- Numérique à 6 chiffres, haché avec SHA-256 + pepper au repos.
- TTL de 10 minutes à compter de l'émission.
- 5 tentatives erronées verrouillent l'invitation (HTTP 423) — l'opérateur doit la réémettre.
- Vérifié une fois à la première utilisation ; une réutilisation depuis la même paire IP / UA saute le contrôle (le token est déjà épinglé).
- Remise de l'URL en face à face (pas de
recipient_email+ pas derecipient_phone) saute l'émission d'OTP — l'URL seule est le facteur d'authentification. À réserver aux remises en personne.
Codes de réponse sur GET /v1/sessions/{id}/join/{token}:
| Statut | Corps | Signification |
|---|---|---|
| 200 | {field_session_token, livekit, ...} | OTP validé (ou non requis) ; la session terrain est en direct. |
| 401 | {detail:"otp_required", channels:[...], channel_hint_email, channel_hint_phone} | La SPA doit afficher le formulaire de saisie de l'OTP. |
| 401 | {detail:"otp_invalid", attempts_remaining} | Code erroné ; affichez les tentatives restantes. |
| 401 | {detail:"otp_expired"} | Fenêtre de 10 min écoulée ; l'opérateur doit réémettre. |
| 423 | {detail:"otp_locked"} | 5 tentatives erronées ; l'invitation est morte jusqu'à réémission. |
Erreurs courantes
| Statut | Code | Quand |
|---|---|---|
| 402 | billing.subscription_past_due | L'abonnement Stripe de l'organisation est en retard de paiement. |
| 402 | billing.free_plan_minutes_exhausted | La formule gratuite / pilote a utilisé ses 5 inspections. |
| 403 | permission_denied | L'identifiant ne dispose pas du scope sessions:write Portée. |
| 409 | session.already_ended | Tentative de terminer une session déjà fermée (rare — `end` est normalement idempotent). |
| 429 | rate_limited | Limite de débit de 60 rpm par identifiant ou 600 rpm par organisation atteinte. Voir l'en-tête X-RateLimit-Reset . |
Voir Erreurs + limites de débit pour la forme complète de l'enveloppe d'erreur et les conseils de nouvelle tentative.