Guichet v1 · contrat stable
Le formulaire du site parle au cabinet, et à personne d'autre.
Une clé, un cabinet. Le site dépose une demande signée ; il ne relit jamais un patient, jamais une tournée, jamais une donnée de santé. Cette page est la notice complète — elle est produite par le code qui répond, et suit donc chaque évolution.
Fenêtre anti-rejeu
300 s
Assez pour un relais lent, trop peu pour un rejeu utile.
Algorithme
HMAC-SHA256 (hex)
Comparaison en temps constant : la longueur du préfixe correct ne fuit pas.
Corps signé
le corps brut
On vérifie avant de parser : une signature sur du JSON re-sérialisé ne prouve rien.
Horodatage
secondes ou millisecondes
Les deux dialectes sont acceptés, à vérifications strictement identiques.
Taille maximale du corps
64 000 octets
Un formulaire de contact n'a pas besoin de plus, et une pièce jointe passe ailleurs.
Les portes
Chaque porte demande la portée indiquée. Une clé ne peut que ce que le cabinet lui a accordé.
| Appel | Portée | Ce qu'elle fait |
|---|---|---|
| POST /api/public/v1/intake succès 201 · conflit 409 | intakesigne le corps | Déposer une arrivée. Le site du cabinet transmet une demande signée. La référence sert de clé d'idempotence : deux envois identiques ne créent qu'une demande. |
| GET /api/public/v1/intake/{tracking_token}/status | intakesigne la cible | Relire l'état d'une arrivée. Rend le statut normalisé et l'horodatage. Aucune donnée de santé, aucun patient, aucune tournée n'en sort jamais. |
| GET /api/public/v1/intake/{tracking_token}/statut | intakesigne la cible | Relire l'état (orthographe française). Exactement la même réponse que `/status`. Servie à l'identique, sans date de fin : un client écrit hier fonctionne demain. |
| GET /api/public/v1/availability | availabilitysigne la cible | Voir les fenêtres ouvertes. Rend des fenêtres (matin, milieu de journée, soir) et leur tension — jamais l'agenda, jamais un nom, jamais un patient. |
| POST /api/public/v1/holds succès 201 · conflit 409 | holdssigne le corps | Retenir un créneau. Pose une retenue temporaire. Elle n'est pas un rendez-vous : le cabinet confirme, ou la retenue expire d'elle-même. |
| GET /api/public/v1/holds/{hold_token} | holdssigne la cible | Relire une retenue. Dit si la retenue tient encore, a été confirmée, libérée ou annulée. |
| GET /api/public/v1/contrat | lecturesigne la cible | Lire le contrat, par la machine. Le même contenu que cette notice, en JSON : schémas des canaux, en-têtes, fenêtre anti-rejeu, codes d'erreur. |
| PAGE /ma-demande/{tracking_token} | intakeaucune signature | La page de suivi de la personne. L'adresse à donner au visiteur après son dépôt. Elle s'ouvre sans compte et ne montre rien d'autre que l'avancement. |
Signer, puis vérifier
La matière signée est l'horodatage, un point, puis le corps brut. Une signature calculée sur du JSON re-sérialisé ne prouve rien : c'est l'erreur la plus fréquente, et ce banc l'écarte en trente secondes.
Banc de signature
Tout se calcule ici, dans votre navigateur. Le secret n'est ni envoyé, ni journalisé, ni conservé — quittez la page et il disparaît.
Matière donnée à HMAC-SHA256
1788106854.{
"accountId": "votre-account-id",
"channel": "soins_domicile",
"reference": "site-2026-000418",
"submitted_at": "2026-08-26T09:12:00.000Z",
"consent": true,
"payload": {
"nom": "Dupont",
"prenom": "Camille",
"telephone": "+33 6 12 34 56 78",
"commune": "Grenoble",
"message": "Pansement à refaire, sortie d'hospitalisation."
}
}Les échos sortants
Le cabinet peut déclarer une adresse HTTPS : chaque changement d'état y est frappé, signé de la même façon, avec reprise progressive en cas de panne.
intake.receivedDemande reçue
intake.reviewingDemande lue par le cabinet
intake.scheduledPassage planifié
intake.completedPassage effectué
intake.closedDemande clôturée
hold.createdCréneau retenu
hold.confirmedCréneau confirmé
hold.releasedCréneau libéré
Le vocabulaire
Listes fermées : un client peut les coder en dur sans risque. On ajoute, on n'ôte pas.
status (à tester)
receivedreviewingscheduledcompletedclosedstate (nuance interne)
recueluecreneau_retenuplanifieeclotureeslot_windows
morningmiddayeveningwindow_capacity
opentightclosedhold_statuses
heldconfirmedexpiredcancelledQuand ça refuse
Chaque erreur porte trois champs : error (le mot normalisé, à tester), code (notre code interne, journalisable) et message (la phrase française).
| error | code | Ce qu'il faut corriger |
|---|---|---|
| unauthorized | signature_invalide | Recalculez la signature sur le corps brut, octet pour octet, avant toute re-sérialisation. |
| unauthorized | cle_inconnue | L'identifiant de clé n'est pas reconnu : vérifiez l'en-tête `key-id`. |
| forbidden | cle_revoquee | Cette clé a été retirée par le cabinet. Demandez-lui d'en émettre une nouvelle. |
| forbidden | portee_refusee | La clé n'a pas cette portée. Le cabinet peut l'accorder depuis son atelier. |
| forbidden | origine_refusee | L'origine du navigateur n'est pas déclarée. Déposez de serveur à serveur, ou faites déclarer l'origine. |
| channel_closed | canal_ferme | Le cabinet a fermé ce canal. Proposez-en un autre, ou retirez le formulaire. |
| account_mismatch | destinataire_invalide | Le `accountId` de l'enveloppe ne correspond pas au compte porteur de la clé. |
| invalid_json | corps_illisible | Le corps n'est pas du JSON valide : cherchez du côté de la sérialisation. |
| invalid_payload | requete_invalide | Un champ manque ou dépasse sa taille : la réponse nomme lequel. |
| not_found | route_inconnue | Cette adresse n'existe pas. La liste des portes est ci-dessus. |
| not_found | introuvable | Le jeton ne désigne rien, ou plus rien. Ne le rejouez pas. |
| conflict | creneau_indisponible | Le créneau était libre, il ne l'est plus. Relisez les fenêtres et reproposez. |
| conflict | reference_connue | Cette référence a déjà servi : la demande existe, elle n'est pas recréée. |
| rate_limited | quota_depasse | Ralentissez : les plafonds par minute et par jour figurent dans le contrat. |
| unavailable | indisponible | Panne passagère de notre côté. Réessayez avec un retrait progressif. |