{"openapi":"3.1.0","info":{"title":"Passerelle WhatsApp","version":"1.0.0","summary":"API de messagerie pour comptes personnels et comptes professionnels mobiles","description":"Cette API expose des comptes de messagerie connectés en tant qu'appareil compagnon, sans passer par la plateforme officielle de l'éditeur.\n\nPoints structurants à connaître avant intégration :\n\n1. Livraison des événements garantie au moins une fois. Le champ idempotence et l'en-tête x-evenement-id permettent au client de dédupliquer.\n2. Les envois sont asynchrones. L'API répond 202 avec un identifiant de suivi, puis le planificateur respecte la cadence, le bruit aléatoire et la fenêtre horaire.\n3. Les garde-fous anti-blocage sont appliqués dans le cœur du service et ne sont pas contournables par l'appelant.\n4. Un verrou de prise de contact peut être appliqué par la messagerie. Il se traduit par le code métier restriction_contact et par le code serveur 463. La session reste saine : il ne faut ni la redémarrer ni la réappairer.\n5. Une bascule de moteur impose un nouvel appairage par code QR, car les formats de persistance de l'état cryptographique ne sont pas interopérables.\n6. L'appairage peut exiger une signature de type WebAuthn, qui ne peut être produite que dans un navigateur sur l'origine officielle de la messagerie.\n\nAucune garantie de non-blocage ne peut être fournie : les conditions d'utilisation de la messagerie interdisent les clients non officiels.","contact":{"name":"Support technique"}},"servers":[{"url":"/","description":"Instance courante"}],"tags":[{"name":"Comptes","description":"Cycle de vie des sessions, appairage, bascule de moteur"},{"name":"Messages","description":"Émission et suivi de livraison"},{"name":"Conversations","description":"État de lecture et historique"},{"name":"Contacts","description":"Annuaire et vérification de numéro"},{"name":"Groupes","description":"Métadonnées de groupe avec cache"},{"name":"Garde-fous","description":"Quotas, phases, liste de suppression, score de santé"},{"name":"Webhooks","description":"Souscription, signature, livraisons et rejeu"},{"name":"Proxies","description":"Adresses de sortie dédiées et contrôle de leur santé"},{"name":"Veille","description":"Comptes canaris, versions épinglées et procédure de mise à jour"},{"name":"Administration","description":"Console d'exploitation, audit et rotation des clés"},{"name":"Bac à sable","description":"Leviers de simulation permettant d'éprouver chaque incident sans risque"},{"name":"Système","description":"Santé, métriques, moteurs et flux temps réel"}],"components":{"securitySchemes":{"cleApi":{"type":"http","scheme":"bearer","description":"Clé d'API du locataire, transmise en en-tête Authorization. Elle n'est stockée que sous forme hachée."},"consoleAdmin":{"type":"http","scheme":"basic","description":"Identifiants de la console d'exploitation. Ils ne donnent accès qu'aux opérations d'administration, jamais au contenu des conversations."}},"schemas":{"Erreur":{"type":"object","properties":{"erreur":{"type":"boolean","example":true},"code":{"type":"string","example":"quota_depasse"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}},"required":["erreur","code","message"]},"Compte":{"type":"object","properties":{"id":{"type":"string","example":"acc_7K2M4Q9XR3TVBN"},"libelle":{"type":"string"},"numero":{"type":["string","null"]},"moteur":{"type":"string","enum":["simulation","gows"]},"statut":{"type":"string","enum":["creee","initialisation","attente_appairage","passkey_requise","passkey_confirmation","synchronisation","connectee","connectee_restreinte","reconnexion","degradee","desappairee","echec","arretee"]},"phase":{"type":"string","enum":["amorcage","echauffement","consolidation","croisiere","restriction"]},"score_sante":{"type":"integer","minimum":0,"maximum":100},"gele":{"type":"boolean"},"restriction":{"type":"object","properties":{"active":{"type":"boolean"},"fin_le":{"type":["string","null"],"format":"date-time"},"type":{"type":["string","null"]}}},"quotas":{"type":"object","properties":{"envois_utilises":{"type":"integer"},"envois_plafond":{"type":"integer"},"nouveaux_utilises":{"type":"integer"},"nouveaux_plafond":{"type":"integer"}}},"proxy_id":{"type":["string","null"]},"fenetre_horaire":{"type":"string","example":"08:00-20:00"},"fuseau":{"type":"string","example":"Africa/Algiers"},"cree_le":{"type":"string","format":"date-time"}}},"DemandeEnvoi":{"type":"object","required":["compte_id","destinataire"],"properties":{"compte_id":{"type":"string"},"destinataire":{"type":"string","description":"Numero au format international, ou identifiant de groupe","example":"+213770000000"},"type":{"type":"string","enum":["texte","media"],"default":"texte"},"texte":{"type":"string","description":"Peut contenir des variables au format double accolade"},"variables":{"type":"object","additionalProperties":{"type":"string"}},"citer":{"type":["string","null"],"description":"Identifiant du message cite"},"indicateur_saisie":{"type":"boolean","default":true},"ignorer_fenetre":{"type":"boolean","default":false,"description":"Reserve aux messages transactionnels urgents"}}},"ReponseEnvoi":{"type":"object","properties":{"id":{"type":"string"},"statut":{"type":"string","enum":["accepte"]},"planifie_pour":{"type":"string","format":"date-time"},"compte_id":{"type":"string"},"conversation_id":{"type":"string"},"controles":{"type":"array","description":"Trace complète de la chaîne de contrôle appliquée avant acceptation","items":{"type":"object","properties":{"ordre":{"type":"integer"},"cle":{"type":"string"},"libelle":{"type":"string"},"resultat":{"type":"string","enum":["ok","refus","differe"]},"detail":{"type":"string"}}}}}},"Evenement":{"type":"object","description":"Charge utile normalisée, identique quel que soit le moteur","properties":{"schema":{"type":"string","const":"v1"},"evenement":{"type":"string","enum":["compte.cree","compte.appaire","compte.connecte","compte.qr_requis","compte.passkey_requise","compte.passkey_confirmation","compte.desappaire","compte.restriction_contact","compte.moteur_bascule","compte.echec","compte.supprime","message.accepte","message.envoye","message.recu","message.accuse","message.echec","message.reaction","message.edite","message.retire","message.observe","conversation.presence","conversation.saisie","conversation.lue","contact.desinscription"]},"id":{"type":"string"},"compte_id":{"type":["string","null"]},"locataire_id":{"type":"string"},"horodatage":{"type":"string","format":"date-time"},"idempotence":{"type":"string"},"donnees":{"type":"object","additionalProperties":true},"moteur":{"type":"object","properties":{"nom":{"type":"string"},"version_wa":{"type":"string"}}}}},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"},"evenements":{"type":"array","items":{"type":"string"},"example":["message.*","compte.qr_requis"]},"actif":{"type":"boolean"},"echecs_consecutifs":{"type":"integer"},"disjoncteur_ouvert":{"type":"boolean"}}}}},"security":[{"cleApi":[]}],"paths":{"/v1/comptes":{"get":{"tags":["Comptes"],"summary":"Lister les comptes du locataire","responses":{"200":{"description":"Liste des comptes","content":{"application/json":{"schema":{"type":"object","properties":{"comptes":{"type":"array","items":{"$ref":"#/components/schemas/Compte"}}}}}}},"401":{"description":"Authentification requise","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"post":{"tags":["Comptes"],"summary":"Créer un compte et démarrer la session","description":"La session démarre immédiatement et émet un code QR. Le premier code est valable 60 secondes, chacun des suivants 20 secondes, pour un total de six codes.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["libelle"],"properties":{"libelle":{"type":"string"},"numero":{"type":["string","null"]},"moteur":{"type":"string","enum":["simulation","gows"]},"proxy_id":{"type":["string","null"]},"sync_historique":{"type":"boolean","default":false,"description":"Necessite de se presenter comme un client de bureau, ce qui change l'empreinte de connexion"},"fenetre_horaire":{"type":"string","default":"08:00-20:00"},"fuseau":{"type":"string","default":"Africa/Algiers"},"nom_appareil":{"type":"string","description":"Ne s'applique qu'au parcours par code QR"},"navigateur":{"type":"string","enum":["Chrome","Firefox","Safari","Edge","Opera","IE","Desktop"],"description":"Un nom non reconnu fait apparaitre un peripherique generique"}}}}}},"responses":{"201":{"description":"Compte créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Compte"}}}},"400":{"description":"Requete invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/comptes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Comptes"],"summary":"État détaillé du compte","responses":{"200":{"description":"Compte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Compte"}}}},"404":{"description":"Compte inexistant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"patch":{"tags":["Comptes"],"summary":"Modifier un compte existant","description":"Seuls les paramètres d'exploitation sont modifiables : libellé, fenêtre horaire, fuseau et adresse de sortie. L'identité de l'appareil et l'état cryptographique ne le sont jamais, car les altérer romprait la session.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"libelle":{"type":"string"},"fenetre_horaire":{"type":"string","example":"08:00-20:00"},"fuseau":{"type":"string","example":"Africa/Algiers"},"proxy_id":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Compte modifié"},"400":{"description":"Champ invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"404":{"description":"Compte ou proxy inexistant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"delete":{"tags":["Comptes"],"summary":"Déconnecter, purger les clés et supprimer","responses":{"204":{"description":"Supprime"}}}},"/v1/comptes/{id}/qr":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Comptes"],"summary":"Obtenir le code QR courant","description":"Rafraîchir à la réception de l'événement compte.qr_requis, et non selon une minuterie fixe côté client.","responses":{"200":{"description":"Code QR","content":{"application/json":{"schema":{"type":"object","properties":{"valeur":{"type":"string"},"image":{"type":"string","description":"Image PNG encodee en base 64"},"expire_dans":{"type":"integer"},"restants":{"type":"integer"}}}}}},"404":{"description":"Aucun code QR en cours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/comptes/{id}/code-appairage":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Obtenir un code d'appairage à saisir depuis le téléphone","description":"Un nom d appareil personnalise fait echouer ce parcours. Toujours prevoir le code QR en repli.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["telephone"],"properties":{"telephone":{"type":"string"}}}}}},"responses":{"200":{"description":"Code généré","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","example":"ABCD-EFGH"}}}}}}}}},"/v1/comptes/{id}/passkey":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Comptes"],"summary":"Lire le défi WebAuthn en attente","responses":{"200":{"description":"Defi"},"422":{"description":"Aucun defi en attente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"post":{"tags":["Comptes"],"summary":"Soumettre l'assertion WebAuthn","description":"L'assertion doit avoir été signée dans un navigateur sur l'origine officielle de la messagerie. Aucune production côté serveur n'est possible.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Assertion acceptée"}}}},"/v1/comptes/{id}/passkey/confirmer":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Confirmer le code affiché sur le téléphone","responses":{"200":{"description":"Confirme"}}}},"/v1/comptes/{id}/reconnecter":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Forcer une reconnexion","responses":{"200":{"description":"Reconnexion demandee"}}}},"/v1/comptes/{id}/deconnecter":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Désappairer et purger les clés","responses":{"200":{"description":"Desappaire"}}}},"/v1/comptes/{id}/moteur":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Basculer le moteur de session","description":"Impose un nouvel appairage par code QR : les formats de persistance de l'état cryptographique ne sont pas interopérables entre moteurs.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["moteur"],"properties":{"moteur":{"type":"string","enum":["simulation","gows"]}}}}}},"responses":{"200":{"description":"Bascule effectuee","content":{"application/json":{"schema":{"type":"object","properties":{"reappairage_requis":{"type":"boolean","const":true}}}}}}}}},"/v1/messages":{"post":{"tags":["Messages"],"summary":"Émettre un message","description":"Réponse 202 : le message est accepté puis planifié. L'en-tête Idempotency-Key garantit qu'un réessai réseau ne provoque jamais de doublon.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemandeEnvoi"}}}},"responses":{"202":{"description":"Message accepté","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReponseEnvoi"}}}},"409":{"description":"Compte non connecté, destinataire supprimé, ou verrou de prise de contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Numéro absent du réseau ou contenu dupliqué","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"429":{"description":"Quota ou plafond de phase atteint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"451":{"description":"Compte gelé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/messages/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Messages"],"summary":"Statut de livraison","responses":{"200":{"description":"Message"},"404":{"description":"Message inexistant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"patch":{"tags":["Messages"],"summary":"Éditer un message émis","description":"La messagerie n'autorise l'édition que dans les quinze minutes suivant l'envoi. Au-delà, la requête est refusée avec un message explicite plutôt qu'un échec opaque du moteur.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["texte"],"properties":{"texte":{"type":"string"}}}}}},"responses":{"200":{"description":"Message édité"},"400":{"description":"Délai d'édition dépassé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}},"delete":{"tags":["Messages"],"summary":"Supprimer un message pour tous les participants","responses":{"200":{"description":"Message retiré"},"501":{"description":"Moteur sans suppression pour tous","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/conversations":{"get":{"tags":["Conversations"],"summary":"Lister les conversations","parameters":[{"name":"compte_id","in":"query","schema":{"type":"string"}},{"name":"limite","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Conversations"}}}},"/v1/conversations/{id}/messages":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Conversations"],"summary":"Historique d'une conversation","responses":{"200":{"description":"Messages"}}}},"/v1/conversations/{id}/lu":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Conversations"],"summary":"Marquer comme lu","responses":{"200":{"description":"Marque"}}}},"/v1/contacts":{"get":{"tags":["Contacts"],"summary":"Lister les contacts synchronisés","parameters":[{"name":"compte_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Contacts"}}}},"/v1/contacts/verifier":{"post":{"tags":["Contacts"],"summary":"Vérifier en lot la présence de numéros sur le réseau","description":"À appeler systématiquement avant toute prise de contact : émettre vers un numéro absent du réseau est un signal de prospection automatisée.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["compte_id","numeros"],"properties":{"compte_id":{"type":"string"},"numeros":{"type":"array","items":{"type":"string"},"maxItems":100}}}}}},"responses":{"200":{"description":"Resultats"}}}},"/v1/groupes":{"get":{"tags":["Groupes"],"summary":"Lister les groupes","description":"Les métadonnées sont mises en cache. Sans ce cache, chaque envoi dans un groupe déclenche une requête d'annuaire, ce qui provoque une limitation de débit et un risque de blocage.","parameters":[{"name":"compte_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"forcer","in":"query","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Groupes et origine de la réponse"}}}},"/v1/messages/{id}/reaction":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Messages"],"summary":"Réagir à un message émis","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["emoji"],"properties":{"emoji":{"type":"string","example":"👍"}}}}}},"responses":{"200":{"description":"Réaction posée"},"501":{"description":"Moteur sans réactions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/conversations/{id}/saisie":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Conversations"],"summary":"Afficher l'indicateur de saisie","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"actif":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Indicateur mis à jour"}}}},"/v1/comptes/{id}/mode-emission":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Garde-fous"],"summary":"Choisir le mode d'émission du compte","description":"Le risque de blocage provient de l'émission, jamais de la réception. En mode observation, la session reste réellement connectée, reçoit les messages entrants, les accusés et les événements de la messagerie, et chaque envoi traverse l'intégralité de la chaîne de contrôle, mais rien n'est transmis au réseau : le message est marqué observe. C'est le mode à utiliser pour valider une intégration sur un numéro réel sans exposer le compte.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["observation","complet"]}}}}}},"responses":{"200":{"description":"Mode appliqué"},"400":{"description":"Mode inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/comptes/{id}/presence":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Comptes"],"summary":"Déclarer la présence en ligne du compte","description":"À utiliser avec discernement : une présence permanente empêche le téléphone d'afficher les notifications et constitue un signal d'automatisation.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"disponible":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Présence appliquée"}}}},"/v1/suppression":{"get":{"tags":["Garde-fous"],"summary":"Lister la liste de suppression","responses":{"200":{"description":"Liste"}}},"post":{"tags":["Garde-fous"],"summary":"Ajouter un numéro à la liste de suppression","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["numero"],"properties":{"numero":{"type":"string"},"motif":{"type":"string"}}}}}},"responses":{"201":{"description":"Ajoute"}}}},"/v1/suppression/{numero}":{"parameters":[{"name":"numero","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["Garde-fous"],"summary":"Retirer un numéro de la liste","responses":{"204":{"description":"Retire"}}}},"/v1/comptes/{id}/sante":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Garde-fous"],"summary":"Score de santé détaillé par composante","responses":{"200":{"description":"Score et composantes"}}}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"Lister les points de sortie","responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}}}},"post":{"tags":["Webhooks"],"summary":"Déclarer un point de sortie","description":"Le corps est signe par HMAC SHA256 sur la concatenation horodatage, point, corps brut. En-têtes émis : x-signature, x-signature-horodatage, x-evenement-id, x-evenement-type, x-tentative. Fenêtre anti-rejeu de 5 minutes. Réponse 2xx attendue en moins de 5 secondes. Livraison garantie au moins une fois : traitez x-evenement-id de manière idempotente.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"evenements":{"type":"array","items":{"type":"string","enum":["compte.cree","compte.appaire","compte.connecte","compte.qr_requis","compte.passkey_requise","compte.passkey_confirmation","compte.desappaire","compte.restriction_contact","compte.moteur_bascule","compte.echec","compte.supprime","message.accepte","message.envoye","message.recu","message.accuse","message.echec","message.reaction","message.edite","message.retire","message.observe","conversation.presence","conversation.saisie","conversation.lue","contact.desinscription","*","message.*","compte.*"]}}}}}}},"responses":{"201":{"description":"Cree, le secret n est retourne qu une seule fois"}}}},"/v1/webhooks/{id}/tester":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Webhooks"],"summary":"Émettre un événement de test signé","responses":{"200":{"description":"Test planifié"}}}},"/v1/webhooks/{id}/livraisons":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Webhooks"],"summary":"Journal des livraisons","responses":{"200":{"description":"Livraisons"}}}},"/v1/livraisons/{id}/rejouer":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Webhooks"],"summary":"Rejouer une livraison","responses":{"200":{"description":"Replanifiee"}}}},"/v1/stream":{"get":{"tags":["Système"],"summary":"Flux temps réel des événements","description":"Flux au format Server Sent Events, alternative aux webhooks pour les interfaces.","responses":{"200":{"description":"Flux ouvert","content":{"text/event-stream":{}}}}}},"/v1/sante":{"get":{"tags":["Système"],"summary":"Sonde de disponibilité","security":[],"responses":{"200":{"description":"État du service"}}}},"/v1/moteurs":{"get":{"tags":["Système"],"summary":"Moteurs disponibles et matrice de capacités","responses":{"200":{"description":"Moteurs"}}}},"/v1/statistiques":{"get":{"tags":["Système"],"summary":"Indicateurs du locataire","responses":{"200":{"description":"Statistiques"}}}},"/metrics":{"get":{"tags":["Système"],"summary":"Métriques au format Prometheus","security":[],"responses":{"200":{"description":"Exposition texte","content":{"text/plain":{}}}}}},"/openapi.json":{"get":{"tags":["Système"],"summary":"Spécification OpenAPI de cette instance","security":[],"responses":{"200":{"description":"Document OpenAPI 3.1"}}}},"/v1/alertes":{"get":{"tags":["Système"],"summary":"Alertes récentes de la flotte","responses":{"200":{"description":"Alertes"}}}},"/v1/comptes/{id}/geler":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Garde-fous"],"summary":"Geler ou dégeler un compte","description":"Le gel bloque toute émission sans détruire la session. Il est appliqué automatiquement sous un score de santé de 40, et peut être posé ou levé manuellement.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["gele"],"properties":{"gele":{"type":"boolean"},"motif":{"type":"string"}}}}}},"responses":{"200":{"description":"État du gel mis à jour"},"404":{"description":"Compte inexistant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/comptes/{id}/phase":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Garde-fous"],"summary":"Forcer la phase de montée en température","description":"Réservé à la reprise après incident et aux essais. En fonctionnement normal, la phase progresse seule selon l'ancienneté du compte et son score de santé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phase"],"properties":{"phase":{"type":"string","enum":["amorcage","echauffement","consolidation","croisiere","restriction"]}}}}}},"responses":{"200":{"description":"Phase appliquée"},"400":{"description":"Phase inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/comptes/{id}/recalculer-sante":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Garde-fous"],"summary":"Recalculer immédiatement le score de santé","responses":{"200":{"description":"Score recalculé"}}}},"/v1/gardefous":{"get":{"tags":["Garde-fous"],"summary":"Référence des garde-fous appliqués","description":"Expose la chaîne des neuf contrôles, le programme de montée en température et la consommation de quota du locataire. Utile pour expliquer un refus sans accès aux journaux.","responses":{"200":{"description":"Phases, chaîne de contrôle et quotas"}}}},"/v1/suppression/verifier/{numero}":{"parameters":[{"name":"numero","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Garde-fous"],"summary":"Vérifier la présence d'un numéro sur la liste de suppression","responses":{"200":{"description":"Présence"}}}},"/v1/consentements":{"post":{"tags":["Garde-fous"],"summary":"Enregistrer un consentement de contact","description":"Trace l'origine du consentement, sa date et sa preuve. Le registre est la contrepartie de la liste de suppression : il justifie une prise de contact.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["numero","origine"],"properties":{"numero":{"type":"string"},"origine":{"type":"string"},"preuve":{"type":"string"}}}}}},"responses":{"201":{"description":"Consentement enregistré"}}}},"/v1/webhooks/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["Webhooks"],"summary":"Retirer un point de sortie","responses":{"204":{"description":"Retiré"}}}},"/v1/webhooks/{id}/disjoncteur":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Webhooks"],"summary":"Refermer le disjoncteur","description":"Le disjoncteur s'ouvre après plusieurs échecs consécutifs afin de ne pas saturer un point de sortie en panne. Cette opération le referme après correction.","responses":{"200":{"description":"Disjoncteur refermé"}}}},"/v1/livraisons":{"get":{"tags":["Webhooks"],"summary":"Journal global des livraisons","parameters":[{"name":"statut","in":"query","schema":{"type":"string","enum":["en_attente","livre","rebut","abandonne"]}},{"name":"limite","in":"query","schema":{"type":"integer","default":100}}],"responses":{"200":{"description":"Livraisons"}}}},"/v1/livraisons/rejouer-rebut":{"post":{"tags":["Webhooks"],"summary":"Rejouer toute la file de rebut","description":"Replanifie les livraisons définitivement échouées, après correction du point de sortie.","responses":{"200":{"description":"Nombre de livraisons replanifiées"}}}},"/v1/evenements":{"get":{"tags":["Webhooks"],"summary":"Journal des événements émis","description":"Charge utile normalisée au schéma v1, strictement identique à celle transmise aux webhooks.","parameters":[{"name":"limite","in":"query","schema":{"type":"integer","default":100}},{"name":"compte_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Événements"}}}},"/outils/verifier-signature":{"post":{"tags":["Webhooks"],"summary":"Recalculer une signature de webhook","security":[],"description":"Reproduit à l'identique le calcul du distributeur, pour mettre au point une vérification côté client. Aucun secret n'est stocké par cet appel.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["secret","corps"],"properties":{"secret":{"type":"string"},"horodatage":{"type":"string"},"corps":{"type":"string"}}}}}},"responses":{"200":{"description":"Signature attendue et formule appliquée"}}}},"/v1/proxies":{"get":{"tags":["Proxies"],"summary":"Lister les adresses de sortie","responses":{"200":{"description":"Proxies"}}},"post":{"tags":["Proxies"],"summary":"Déclarer une adresse de sortie","description":"Une adresse est affectée à un seul compte. Les proxies rotatifs sont à proscrire : le changement d'adresse en cours de session provoque des déconnexions et un profil incohérent.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["libelle","type","hote","port"],"properties":{"libelle":{"type":"string"},"type":{"type":"string","enum":["residentiel_statique","mobile_4g","passerelle_operateur","datacenter"]},"hote":{"type":"string"},"port":{"type":"integer"},"pays":{"type":"string"},"operateur":{"type":"string"}}}}}},"responses":{"201":{"description":"Proxy déclaré"}}}},"/v1/proxies/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["Proxies"],"summary":"Retirer une adresse de sortie","responses":{"204":{"description":"Retiré"}}}},"/v1/proxies/{id}/controle":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Proxies"],"summary":"Contrôler la santé et la latence d'une adresse","responses":{"200":{"description":"Santé et latence mesurées"}}}},"/v1/canaris":{"get":{"tags":["Veille"],"summary":"Scénarios synthétiques et dernier résultat","responses":{"200":{"description":"Scénarios"}}}},"/v1/canaris/executer":{"post":{"tags":["Veille"],"summary":"Exécuter la campagne synthétique","description":"Système d'alerte précoce : une rupture de protocole se manifeste ici avant de dégrader la flotte de production.","responses":{"200":{"description":"Résultats et synthèse"}}}},"/v1/veille":{"get":{"tags":["Veille"],"summary":"Versions épinglées et procédure de mise à jour","description":"La version de la bibliothèque et la version de client attendue sont deux paramètres distincts, modifiables sans reconstruction d'image.","responses":{"200":{"description":"Versions et procédure"}}}},"/admin/connexion":{"post":{"tags":["Administration"],"summary":"Ouvrir la console","security":[{"consoleAdmin":[]}],"responses":{"200":{"description":"Authentifié"},"401":{"description":"Identifiants invalides","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/admin/cle-demo":{"get":{"tags":["Administration"],"summary":"Récupérer la clé d'API de démonstration","security":[{"consoleAdmin":[]}],"responses":{"200":{"description":"Clé"}}}},"/admin/vue-ensemble":{"get":{"tags":["Administration"],"summary":"Vue transverse à tous les locataires","security":[{"consoleAdmin":[]}],"responses":{"200":{"description":"Synthèse"}}}},"/admin/audit":{"get":{"tags":["Administration"],"summary":"Journal d'audit","security":[{"consoleAdmin":[]}],"responses":{"200":{"description":"Entrées auditées"}}}},"/admin/locataires":{"post":{"tags":["Administration"],"summary":"Créer un locataire","security":[{"consoleAdmin":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom"],"properties":{"nom":{"type":"string"},"quota_jour":{"type":"integer"}}}}}},"responses":{"201":{"description":"Locataire créé, la clé n'est affichée qu'une fois"}}}},"/admin/rotation-cles":{"post":{"tags":["Administration"],"summary":"Faire tourner les clés maîtresses","security":[{"consoleAdmin":[]}],"description":"Seules les clés de données par compte sont rechiffrées : les états de session ne sont jamais déchiffrés en clair sur disque et aucun réappairage n'est nécessaire.","responses":{"200":{"description":"Nombre de clés rechiffrées"}}}},"/admin/reprise-a-froid":{"post":{"tags":["Administration"],"summary":"Reprendre les sessions après un arrêt total","security":[{"consoleAdmin":[]}],"description":"La reprise est cadencée pour éviter un pic de connexions simultanées, qui serait interprété comme un comportement anormal.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"cadence":{"type":"integer","default":5}}}}}},"responses":{"200":{"description":"Sessions reprises"}}}},"/internal/gows/{compteId}":{"parameters":[{"name":"compteId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Système"],"summary":"Point d'ingestion du sidecar whatsmeow","security":[],"description":"Réservé au sidecar, qui relaie les événements du moteur gows. Protégé par un jeton interne et non exposé publiquement.","responses":{"200":{"description":"Événement ingéré"}}}},"/v1/demo/{id}/scanner":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Simuler le scan du code QR depuis le téléphone","responses":{"200":{"description":"Appairage déclenché"}}}},"/v1/demo/{id}/restriction":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Appliquer un verrou de prise de contact","description":"Reproduit le code serveur 463. La session reste connectée : seuls les correspondants sans conversation établie sont refusés.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"minutes":{"type":"integer","default":60},"type":{"type":"string"}}}}}},"responses":{"200":{"description":"Verrou appliqué"}}}},"/v1/demo/{id}/desappairer":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Simuler une invalidation de session par le serveur","responses":{"200":{"description":"Session désappairée"}}}},"/v1/demo/{id}/coupure":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Simuler une coupure réseau transitoire","responses":{"200":{"description":"Reconnexion engagée"}}}},"/v1/demo/{id}/message-entrant":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Injecter un message entrant","description":"Un texte de désinscription alimente automatiquement la liste de suppression, ce qui permet d'éprouver la détection en langage naturel.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"texte":{"type":"string"},"depuis":{"type":"string"}}}}}},"responses":{"200":{"description":"Message injecté"}}}},"/v1/demo/{id}/reinitialiser-quotas":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bac à sable"],"summary":"Réinitialiser les compteurs de quota","responses":{"200":{"description":"Quotas remis à zéro"}}}}},"x-codes-erreur":[{"http":"400","code":"requete_invalide","description":"Corps non conforme au schéma, champ manquant ou format incorrect"},{"http":"401","code":"authentification_requise","description":"Clé d'API absente, révoquée ou invalide"},{"http":"403","code":"acces_refuse","description":"La ressource appartient à un autre locataire"},{"http":"404","code":"ressource_absente","description":"Compte, conversation ou message inexistant"},{"http":"409","code":"compte_non_connecte","description":"La session n'est pas dans un état permettant l'émission"},{"http":"409","code":"destinataire_supprime","description":"Le destinataire figure sur la liste de suppression du locataire"},{"http":"409","code":"restriction_contact","description":"Verrou de prise de contact actif, code serveur 463"},{"http":"422","code":"numero_absent_du_reseau","description":"Le numéro n'est pas enregistré sur la messagerie"},{"http":"422","code":"contenu_duplique","description":"Contenu strictement identique déjà émis dans la fenêtre de contrôle"},{"http":"429","code":"quota_depasse","description":"Quota de locataire, de compte ou de phase de montée en température atteint"},{"http":"451","code":"compte_gele","description":"Compte gelé automatiquement par le score de santé ou manuellement"},{"http":"501","code":"fonction_non_supportee","description":"Le moteur courant ne prend pas en charge cette fonction"},{"http":"503","code":"moteur_indisponible","description":"Aucun travailleur disponible ou moteur en cours de bascule"}],"x-evenements":["compte.cree","compte.appaire","compte.connecte","compte.qr_requis","compte.passkey_requise","compte.passkey_confirmation","compte.desappaire","compte.restriction_contact","compte.moteur_bascule","compte.echec","compte.supprime","message.accepte","message.envoye","message.recu","message.accuse","message.echec","message.reaction","message.edite","message.retire","message.observe","conversation.presence","conversation.saisie","conversation.lue","contact.desinscription"]}