Aller au contenu principal

Cycle de vie d’un appel

Ce document décrit le cycle de vie complet d’un appel téléphonique assisté par IA au sein de la plateforme Audact, du contrôle de conformité avant la numérotation jusqu’à la vérification après l’appel.

Phase 1 : avant la numérotation (moteur de politique)

POST /v1/evaluate — Contrôle de conformité sans état. Aucun événement de preuve n’est écrit.

Le moteur de politique évalue les règles propres à chaque pays (exigences de consentement, registre DNC, plages horaires de tranquillité) et renvoie ALLOW ou BLOCK avec les motifs. En cas de BLOCK, l’appel ne peut pas avoir lieu.

GET /v1/policies/{country} — Consulter l’ensemble de règles actif pour un pays (ISO 3166-1 alpha-2). Renvoie le schéma complet des règles, y compris les plages horaires de tranquillité, les exigences de consentement et la configuration du registre DNC.

Phase 2 : début de l’appel

POST /v1/events avec event_type: CALL_START

La plateforme publie un événement CALL_START. La couche de preuves crée automatiquement une ligne calls avec status = 'active' si elle n’existe pas encore.

Phase 3 : premier tour — information obligatoire

POST /v1/validate (tour 1)

Au premier tour, la porte d’information se déclenche, car call_state.disclosure_delivered == false. Le validateur renvoie verdict: BLOCK avec le texte d’origine dans le champ content. Les 6 autres contrôles ne sont pas exécutés (court-circuit). La plateforme doit énoncer son propre texte d’information, puis définir disclosure_delivered = true dans l’état de l’appel pour les tours suivants.

Phase 4 : conversation — validateur actif

POST /v1/validate (tour N, répété à chaque tour)

Le validateur exécute 7 contrôles dans le cadre d’une échéance stricte de 40 ms :

#ContrôleMode
0Porte d’informationToujours appliqué ; court-circuite en cas de BLOCK
1Analyse de la liste de blocageShadow ou enforce (reconnaissance de motifs Aho-Corasick)
2Fourchette de prixShadow ou enforce (détection des prix par expression régulière + contrôle de la plage)
3Analyse des données personnellesShadow ou enforce (BSN, IBAN, carte bancaire via Luhn)
4Détection d’escaladeShadow ou enforce (mots-clés + score de confiance)
5Filtrage par consentementShadow ou enforce (correspondance avec le registre actions-consentements)
6Cohérence entre les toursEnforce (cohérence des prix, des faits et des engagements)

Verdicts

Il n’existe que trois verdicts : ALLOW, BLOCK, MODIFY. Il n’y a pas de verdict ESCALATE.

  • ALLOW — Le texte d’origine est renvoyé dans content. Reçu : input_hash == output_hash.
  • MODIFY — Le texte modifié est renvoyé dans content. Reçu : input_hash (original), output_hash (modifié).
  • BLOCK — Le texte d’origine figure dans content ; l’appelant le remplace par sa propre réponse de repli. Reçu : input_hash (original), output_hash (réponse de repli).

Forme de la réponse (ACVFResponse)

json

{
  "call_id": "uuid",
  "turn": 1,
  "verdict": "ALLOW | BLOCK | MODIFY",
  "content": "validated or modified text",
  "fail_open": false,
  "receipt": { "..." },
  "compliance_score": { "..." },
  "checks": [
    {
      "check_id": "pii_scan",
      "verdict": "ALLOW",
      "reason": "",
      "modified_content": "",
      "duration_ms": 2
    }
  ]
}

Le proxy LLM écrit un événement VALIDATION_RESULT dans la couche de preuves (via POST /v1/events) après chaque validation. Un reçu de conformité est joint à toutes les réponses qui ne sont pas en repli ouvert (fail-open).

Repli en mode ouvert (fail-open) : délai du validateur dépassé (> 40 ms)

Si le validateur dépasse l’échéance de 40 ms : verdict: ALLOW, fail_open: true, content: original text, receipt: nil. Un événement FAIL_OPEN est écrit immédiatement pour alimenter les alertes, puis conservé durablement de façon asynchrone. Le champ fail_open_meta comprend reason, deadline_ms, elapsed_ms et checks_completed.

Détection des données personnelles (contrôle 4)

Détecte les BSN (numéro néerlandais à 9 chiffres), les IBAN et les numéros de carte bancaire (algorithme de Luhn). Renvoie MODIFY ou BLOCK selon la configuration du tenant.

Phase 4b : validation audio native (chemin alternatif)

POST /v1/validate-audio — Validateur audio

Pour les appels vocaux où la plateforme envoie de l’audio brut au lieu d’un texte déjà transcrit. Le validateur audio :

  1. Accepte de l’audio PCM encodé en base64 (16 bits little-endian, 16 kHz, mono).
  2. Effectue la transcription ASR (Deepgram, Whisper ou AssemblyAI — configurable par tenant via BYOK).
  3. Segmente l’audio selon l’une des deux stratégies :
    • asr_first (par défaut) — L’ASR en flux continu pilote la détection des limites de phrase.
    • prosodic_first — La détection de silence VAD crée les segments, puis l’ASR transcrit chacun d’eux par lot.
  4. Exécute le même pipeline de 7 contrôles sur le texte transcrit (en déléguant au validateur).
  5. Détermine l’acheminement de l’audio : transmettre (ALLOW), substituer (MODIFY — remplace par un TTS généré à l’avance) ou bloquer.
  6. Renvoie des résultats par segment avec deux empreintes (audio_hash + text_hash) et un reçu de conformité portant une balise d’intégrité HMAC.

ASR en parallèle

Lorsque parallel_asr: true, le validateur audio soumet l’audio à plusieurs modèles ASR simultanément, enregistre tous les résultats dans la chaîne de preuves et retient la transcription dont la confiance est la plus élevée.

Forme de la requête

json

{
  "call_id": "uuid",
  "audio_data": "base64-encoded PCM bytes",
  "audio_format": "pcm_s16le_16000",
  "turn_number": 1,
  "configuration_id": "uuid",
  "call_context": {
    "language": "nl",
    "disclosure_delivered": true
  },
  "segmentation_method": "asr_first",
  "parallel_asr": false
}

Forme de la réponse

json

{
  "verdict": "ALLOW",
  "segments": [
    {
      "segment_index": 0,
      "audio_hash": "sha256hex",
      "transcribed_text": "ASR output",
      "text_hash": "sha256hex",
      "verdict": "ALLOW",
      "check_results": [...],
      "segmentation_method": "asr_first",
      "asr_confidence": 0.97,
      "asr_model_id": "deepgram:nova-2",
      "action": "forward"
    }
  ],
  "fail_open": false,
  "evidence_event_id": "uuid",
  "evidence_hash": "sha256hex",
  "receipt": {
    "input_hash": "sha256hex (original audio)",
    "output_hash": "sha256hex (forwarded/substituted audio)",
    "text_hash": "sha256hex (transcription)",
    "verdict": "ALLOW",
    "signature": "HMAC-SHA256"
  }
}

Phase 5 : événements en cours d’appel

POST /v1/events — La couche de preuves accepte toute chaîne event_type. Types courants :

Type d’événementDescription
CALL_STARTMarque le début de l’appel (crée automatiquement la ligne calls)
VALIDATION_RESULTÉcrit par le proxy LLM après chaque appel à /v1/validate
AUDIO_VALIDATIONÉcrit par le validateur audio après chaque appel à /v1/validate-audio
CHECK_RESULTRésultat d’un contrôle individuel
FAIL_OPENÉvénement de dépassement du délai du validateur
DELIVERY_ATTESTATIONConfirmation de remise liée à l’événement de validation
TRANSCRIPT_RECONCILIATIONRésultat de la comparaison de la transcription après l’appel
CALL_ENDMarque la fin de l’appel
CALL_FAILEDMarque l’échec de l’appel

Tous les événements sont ajoutés à la chaîne de hachage cryptographique, quel que soit leur type. Des types d’événements personnalisés (par exemple tool_call, consent_granted) peuvent être envoyés librement.

Phase 5b : proxy LLM

POST /v1/proxy/openai — Proxy LLM sensible à la conformité, sans streaming. POST /v1/proxy/openai/stream — Proxy LLM sensible à la conformité, avec streaming.

Le proxy LLM se place entre la plateforme et le fournisseur LLM en amont. Pour chaque requête :

  1. Il valide la requête via le validateur.
  2. Il transmet les requêtes autorisées au LLM en amont (API compatible OpenAI).
  3. Il valide la réponse du LLM avant de la renvoyer à la plateforme.
  4. Il écrit des événements de preuve pour la validation de la requête comme pour celle de la réponse.
  5. Il prend en charge la génération à candidats multiples : il envoie le même prompt à N points de terminaison en parallèle, valide tous les candidats et renvoie la meilleure réponse conforme.

Phase 6 : attestation de remise

POST /v1/delivery-confirmation — Soumettre une confirmation de remise.

Une fois la réponse validée remise à l’interlocuteur humain, la plateforme (ou le moniteur audio d’Audact) envoie une confirmation de remise. Cela crée un lien à détection d’altération entre le contenu validé et ce qui a effectivement été remis — toute discordance est détectable par recalcul par rapport à l’engagement conservé :

  • DC-1 (webhook) : la plateforme envoie un webhook avec l’empreinte du contenu remis.
  • DC-2 (api_query) : Audact interroge l’API de la plateforme pour connaître l’état de la remise.
  • DC-3 (tts_completion) : le moteur TTS signale la fin de la synthèse avec l’empreinte du contenu.
  • DC-4 (audio_monitoring) : l’ASR propre à Audact capte ce qui a été prononcé (indépendamment de la plateforme auditée).

Chaque confirmation comprend :

  • linking_hash = SHA-256(validation_event_hash || confirmation_hash) — maillon de la chaîne de preuves à détection d’altération.
  • confirmation_grade : independent (DC-4), platform_sourced (DC-1/2/3) ou unconfirmed (délai dépassé).
  • content_match : indique si le contenu remis correspond au contenu validé.

Les confirmations de remise qui n’arrivent pas dans un délai de 30 secondes sont automatiquement marquées unconfirmed.

GET /v1/calls/{call_id}/delivery — Obtenir l’état de l’attestation de remise, tour par tour, pour un appel.

Phase 7 : fin de l’appel

La plateforme envoie un événement terminal via POST /v1/events :

  • CALL_END — Définit calls.status = 'completed' et calls.ended_at de façon atomique, dans la même transaction que l’écriture de l’événement de preuve.
  • CALL_FAILED — Définit calls.status = 'failed' et calls.ended_at de façon atomique, dans la même transaction que l’écriture de l’événement de preuve.

Important : il n’existe AUCUNE tâche de balayage en arrière-plan. Les appels qui ne reçoivent jamais d’événement CALL_END ou CALL_FAILED restent indéfiniment à status = 'active'. C’est à la plateforme d’envoyer les événements terminaux.

Phase 7b : justificatifs de contenu C2PA

POST /v1/calls/{call_id}/c2pa — Générer un manifeste C2PA pour un appel terminé.

Une fois un appel terminé, les tenants peuvent demander un manifeste de justificatif de contenu compatible C2PA. Le manifeste contient :

  • Déclaration (claim) : métadonnées de l’appel, résumé de la chaîne de preuves, empreinte de configuration (config_seal_hash).
  • Assertions : références d’empreinte par événement (aucune donnée d’événement brute), preuve d’intégrité de la chaîne.
  • Code d’authentification : HMAC-SHA256 sur le JSON canonique (RFC 8785) de la déclaration et des assertions, avec la clé du tenant gérée par KMS.
  • Référence de preuve de Merkle : si les preuves de l’appel ont été incluses dans une agrégation entre tenants, le manifeste renvoie à la preuve de branche et à la racine de Merkle.

Le point de terminaison vérifie l’appartenance de l’appel avant de générer le manifeste. Le matériel de clé HMAC n’est jamais inclus dans la réponse.

Phase 7c : agrégation entre tenants

Le service d’agrégation collecte périodiquement les empreintes de preuves de tous les tenants et construit un arbre de Merkle pour la détection d’altération et l’auditabilité par des tiers.

Pipeline d’agrégation (automatique ou manuel)

POST /v1/admin/aggregate — Point de terminaison réservé aux administrateurs, pour déclencher manuellement l’agrégation sur une fenêtre de temps. Exige le rôle owner. Si aucune fenêtre n’est précisée, il agrège le dernier intervalle terminé.

Le pipeline :

  1. Collecte les empreintes agrégées par tenant (SHA-256 de toutes les empreintes de preuves de la fenêtre).
  2. Construit un arbre de Merkle binaire équilibré, avec un remplissage minimal de 16 feuilles.
  3. Demande à une TSA un jeton d’horodatage RFC 3161 pour la racine de Merkle.
  4. Ajoute la racine à la métachaîne (chaîne de chaînes).

Récupération des preuves (authentifiée)

MéthodeCheminDescription
GET/v1/proofsListe les fenêtres d’agrégation auxquelles le tenant a participé (de la plus récente à la plus ancienne)
GET/v1/proofs/{window_id}Récupère la preuve de branche de Merkle et le jeton d’horodatage RFC 3161 pour une fenêtre donnée

Les preuves de branche sont reconstruites à la demande à partir des métadonnées conservées. Les étapes de la preuve ne contiennent que des empreintes de nœuds frères opaques et leur position — aucun identifiant de tenant n’est divulgué.

Vérification publique (sans authentification)

MéthodeCheminDescription
GET/v1/metachainMétachaîne complète (chaîne de chaînes) des racines de Merkle, par ordre croissant
POST/v1/verify-inclusionVérification cryptographique hors ligne d’une preuve de branche de Merkle

La métachaîne permet à quiconque de vérifier l’intégrité entre agrégations :

  • chain_hash = SHA-256(sequence || merkle_root || previous_hash)
  • Entrée de genèse : previous_hash = 64 zéros
  • La chaîne applique le contrôle d’intégrité à chaque ajout

Le point de terminaison verify-inclusion fonctionne entièrement hors ligne — sans accès à la base de données, sans authentification. Transmettez-lui directement l’objet branch_proof obtenu avec GET /v1/proofs/{window_id}.

Phase 8 : vérification après l’appel

Point de terminaisonObjet
GET /v1/verify/{call_id}Parcourt la chaîne de preuves, recalcule l’empreinte SHA-256 de chaque événement et vérifie les liens previous_hash. Renvoie VALID ou INVALID avec broken_at_sequence.
GET /v1/chains/{call_id}Récupération paginée de la chaîne de preuves. Paramètres : after_sequence, limit.
GET /v1/reconstruct/{call_id}Rejoue la chaîne de preuves pour reconstituer la configuration exacte de la politique et du validateur active lors de chaque événement (résolution de config_seal_hash).
GET /v1/receipts/{call_id}Récupère le reçu de conformité d’un appel et en contrôle le HMAC.

Rapprochement de la transcription

POST /v1/transcript-reconciliation — Rapprochement de la transcription après l’appel.

Une fois l’appel terminé, la plateforme peut soumettre la transcription complète après l’appel afin de la rapprocher de la chaîne de preuves. Le rapprocheur compare chaque segment de la transcription aux événements de sortie validés :

  • MATCHED : l’empreinte du segment de transcription correspond à l’empreinte de la sortie validée — remise confirmée.
  • MISMATCHED : l’empreinte du segment de transcription diffère — un événement de preuve transcript_mismatch est généré.
  • UNVALIDATED : le segment de transcription n’a aucun événement de validation correspondant — un événement unvalidated_content est généré (détecte les insertions de la plateforme qui ont contourné Audact).

Rapprochement audio

POST /v1/audio-reconciliation — Rapprochement audio après l’appel.

Combine la transcription ASR et le rapprochement de la transcription dans un seul point de terminaison synchrone. L’appelant envoie l’enregistrement complet de l’appel ; le service le transcrit via l’ASR, puis rapproche la transcription de la chaîne de preuves. C’est le point de terminaison à privilégier lorsque la plateforme dispose de l’audio brut mais pas d’une transcription textuelle.

Ce point de terminaison est synchrone — l’appelant reste bloqué jusqu’à la fin de la transcription et du rapprochement. Pour les longs enregistrements, cela peut prendre plusieurs secondes.

Forme de la requête

json

{
  "call_id": "uuid",
  "audio_data": "base64-encoded audio bytes",
  "audio_format": "pcm_s16le_16000 | wav | mp3",
  "language": "nl",
  "asr_provider": "deepgram"
}
  • audio_data : audio encodé en base64 standard. La limite de corps de requête est de 64 MiB (environ 34 min de PCM à 16 kHz mono).
  • audio_format : pcm_s16le_16000 (PCM brut 16 bits little-endian, 16 kHz, mono), wav ou mp3.
  • language : balise BCP-47 (facultatif, nl par défaut).
  • asr_provider : remplace le fournisseur ASR par défaut (facultatif).
  • Pas de champ tenant_id — l’identité du tenant provient du contexte d’authentification.

Forme de la réponse

json

{
  "call_id": "uuid",
  "asr_model_id": "mock:deterministic-v1",
  "asr_confidence": 0.95,
  "total_segments": 5,
  "matched_segments": 4,
  "mismatched_segments": 1,
  "unvalidated_segments": 0,
  "segment_results": [
    {
      "sequence_number": 1,
      "status": "matched",
      "similarity_score": 1.0,
      "validated_hash": "sha256hex",
      "transcript_hash": "sha256hex"
    }
  ]
}

Déroulement

  1. Authentification par clé API.
  2. Vérification de l’appartenance de l’appel.
  3. Décodage des octets audio en base64.
  4. Transcription de l’audio complet via l’ASR (même client ASR que /v1/validate-audio).
  5. Construction des segments de rapprochement à partir de la sortie de l’ASR.
  6. Rapprochement avec la chaîne de preuves (même rapprocheur que /v1/transcript-reconciliation).
  7. Renvoi des résultats segment par segment.

Gestion des tenants

Ces points de terminaison gèrent les tenants, les clés API et les configurations. Ils ne font pas partie du flux par appel, mais sont nécessaires à la mise en place.

Tenants

MéthodeCheminRôleDescription
POST/v1/tenantsPublic (avec limitation de débit)Créer un tenant
GET/v1/tenants/{tenant_id}ViewerObtenir les détails d’un tenant
PUT/v1/tenants/{tenant_id}OwnerMettre à jour un tenant

Clés API

MéthodeCheminRôleDescription
POST/v1/tenants/{tenant_id}/keysOwnerGénérer une nouvelle clé API
GET/v1/tenants/{tenant_id}/keysDeveloperLister les clés API (préfixe uniquement)
DELETE/v1/tenants/{tenant_id}/keys/{key_id}OwnerRévoquer une clé API

Configurations

MéthodeCheminRôleDescription
POST/v1/configurationsDeveloperCréer une configuration
GET/v1/configurations/{config_id}ViewerObtenir une configuration
PUT/v1/configurations/{config_id}DeveloperMettre à jour une configuration
DELETE/v1/configurations/{config_id}OwnerSupprimer une configuration (suppression logique)
GET/v1/tenants/{tenant_id}/configurationsViewerLister les configurations d’un tenant

Les configurations comprennent :

  • Règles de politique (motifs de liste de blocage, détection des données personnelles, fourchettes de prix, mots-clés d’escalade, portes de consentement)
  • Points de terminaison LLM à candidats multiples (BYOK : URL + clé API par fournisseur, stockées de manière sécurisée)
  • Contraintes de sortie (limites de longueur des réponses, avec préservation du contenu critique pour la conformité)
  • Clés des fournisseurs ASR (BYOK : Deepgram, Whisper, AssemblyAI — clés API stockées de manière sécurisée)

Authentification

MéthodeCheminDescription
POST/v1/auth/exchange-keyÉchanger une clé API contre des jetons JWT d’accès et d’actualisation
POST/v1/auth/refreshActualiser un JWT expiré à l’aide du cookie du jeton d’actualisation

Utilisation

MéthodeCheminRôleDescription
GET/v1/tenants/{tenant_id}/usageViewerObtenir les statistiques d’utilisation de l’API du tenant

Récapitulatif complet des points de terminaison

MéthodeCheminDescription
POST/v1/evaluate« Cet appel peut-il avoir lieu ? » (avant la numérotation, sans état)
GET/v1/policies/{country}Obtenir l’ensemble de règles d’un pays
POST/v1/validate« L’IA peut-elle dire ceci ? » (à chaque tour, échéance de 40 ms)
GET/v1/receipts/{call_id}Récupérer le reçu de conformité (après l’appel)
POST/v1/eventsAjouter un événement à la chaîne (pendant l’appel)
GET/v1/callsLister les appels (filtrés par tenant)
GET/v1/chains/{call_id}Récupérer la chaîne d’événements (après l’appel, paginée)
GET/v1/verify/{call_id}Contrôle d’intégrité de la chaîne (après l’appel)
GET/v1/reconstruct/{call_id}Reconstituer l’historique de configuration (après l’appel / audit)
POST/v1/delivery-confirmationSoumettre une confirmation de remise
GET/v1/calls/{call_id}/deliveryObtenir l’état de l’attestation de remise
POST/v1/transcript-reconciliationRapprochement de la transcription après l’appel
POST/v1/proxy/openaiProxy LLM sensible à la conformité (sans streaming)
POST/v1/proxy/openai/streamProxy LLM sensible à la conformité (avec streaming)
POST/v1/tenantsCréer un tenant
GET/v1/tenants/{tenant_id}Obtenir un tenant
PUT/v1/tenants/{tenant_id}Mettre à jour un tenant
POST/v1/tenants/{tenant_id}/keysGénérer une clé API
GET/v1/tenants/{tenant_id}/keysLister les clés API
DELETE/v1/tenants/{tenant_id}/keys/{key_id}Révoquer une clé API
POST/v1/configurationsCréer une configuration
GET/v1/configurations/{config_id}Obtenir une configuration
PUT/v1/configurations/{config_id}Mettre à jour une configuration
DELETE/v1/configurations/{config_id}Supprimer une configuration (suppression logique)
GET/v1/tenants/{tenant_id}/configurationsLister les configurations
GET/v1/tenants/{tenant_id}/usageObtenir les statistiques d’utilisation
POST/v1/auth/exchange-keyÉchanger une clé API contre un JWT
POST/v1/auth/refreshActualiser le JWT
POST/v1/validate-audioValider un segment audio (ASR + conformité)
POST/v1/audio-reconciliationRapprochement audio après l’appel (ASR + rapprochement, synchrone)
POST/v1/calls/{call_id}/c2paGénérer un manifeste de justificatif de contenu C2PA
POST/v1/admin/aggregateDéclencher une agrégation manuelle (owner uniquement)
GET/v1/proofsLister les fenêtres d’agrégation du tenant
GET/v1/proofs/{window_id}Obtenir la preuve de branche de Merkle + le jeton RFC 3161
GET/v1/metachainObtenir la métachaîne des racines de Merkle (public)
POST/v1/verify-inclusionVérifier une preuve d’inclusion de Merkle (public)
GET/v1/healthContrôle de santé

Propriétés des preuves

Tous les événements de preuve sont :

  • En ajout seul — Aucune mise à jour, aucune suppression
  • Chaînés cryptographiquement — SHA-256 sur une préimage délimitée par des barres verticales : event_id|call_id|sequence|event_type|timestamp|canonicalJSON(data)|previous_hash
  • Horodatés côté serveur — time.Now().UTC(), jamais fourni par le client
  • Protégés en écriture — Les écritures concurrentes sur le même call_id sont sérialisées
  • Liés à la configuration — Chaque événement comprend config_seal_hash (SHA-256 du JSON canonique RFC 8785 de la configuration du tenant)

Le cache de l’état de l’appel est dérivé de la chaîne de preuves, et non indépendant. En cas d’absence dans le cache : rejouer les événements depuis le magasin, reconstruire l’état, actualiser le cache.

Modèle de sécurité

  • Cloisonnement des tenants : chaque requête est authentifiée par clé API → l’identité du tenant est extraite du contexte d’authentification. Aucun tenant_id dans les corps de requête.
  • Appartenance de l’appel : l’appartenance de call_id au tenant authentifié est vérifiée avant tout traitement.
  • Intégrité des preuves : les colonnes de la chaîne sont en ajout seul ; toute altération est détectable par recalcul par rapport aux engagements conservés.
  • Gestion des secrets : les clés API des LLM et de l’ASR sont stockées de manière sécurisée ; dans les réponses GET, les clés sont masquées et remplacées par has_key: true.
  • Aucune donnée personnelle dans les journaux : les transcriptions, les numéros de téléphone et le contenu audio ne sont jamais journalisés. Seuls les empreintes et les métadonnées figurent dans les journaux structurés.

Source mise à jour: 19 octobre 2026