Skip to main content
Audact
See the offer

Call lifecycle

This document describes the complete lifecycle of a single AI-assisted phone call through the Audact platform, from pre-dial compliance check through post-call verification.

Phase 1: Pre-Dial (Policy Engine)

POST /v1/evaluate — Stateless compliance check. No evidence events are written.

The Policy Engine evaluates country-specific rules (consent requirements, DNC registry, quiet hours) and returns ALLOW or BLOCK with reasons. If BLOCK, the call may not proceed.

GET /v1/policies/{country} — Look up the active policy ruleset for a country (ISO 3166-1 alpha-2). Returns the full rule schema including quiet hours, consent requirements, and DNC registry configuration.

Phase 2: Call Start

POST /v1/events with event_type: CALL_START

The platform posts a CALL_START event. The Evidence Layer auto-creates a calls row with status = 'active' if one does not already exist.

Phase 3: First Turn — Disclosure Required

POST /v1/validate (turn 1)

On the first turn, the disclosure gate fires because call_state.disclosure_delivered == false. The validator returns verdict: BLOCK with the original text in the content field. The remaining 6 checks are not executed (short-circuit). The platform must speak its own disclosure text and set disclosure_delivered = true in call state for subsequent turns.

Phase 4: Conversation — Validator Active

POST /v1/validate (turn N, repeats per turn)

The Validator runs 7 checks within a 40ms hard deadline:

#CheckMode
0Disclosure gateAlways enforce; short-circuits on BLOCK
1Blocklist scanShadow or enforce (Aho-Corasick pattern matching)
2Pricing bandShadow or enforce (regex price detection + range check)
3PII scanShadow or enforce (BSN, IBAN, credit card via Luhn)
4Escalation detectionShadow or enforce (keyword + confidence scoring)
5Consent gatingShadow or enforce (action-consent ledger mapping)
6Cross-turn consistencyEnforce (price/fact/commitment consistency)

Verdicts

Only three verdicts exist: ALLOW, BLOCK, MODIFY. There is no ESCALATE verdict.

  • ALLOW — Original text returned in content. Receipt: input_hash == output_hash.
  • MODIFY — Modified text returned in content. Receipt: input_hash (original), output_hash (modified).
  • BLOCK — Original text in content; caller substitutes its own fallback response. Receipt: input_hash (original), output_hash (fallback).

Response Shape (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
    }
  ]
}

The LLM Proxy writes a VALIDATION_RESULT event to the Evidence Layer (via POST /v1/events) after each validation. A compliance receipt is attached to all non-fail-open responses.

Fail-Open (Validator Timeout > 40ms)

If the validator exceeds the 40ms deadline: verdict: ALLOW, fail_open: true, content: original text, receipt: nil. A FAIL_OPEN event is written immediately for alerting and durably stored asynchronously. The fail_open_meta field includes reason, deadline_ms, elapsed_ms, and checks_completed.

PII Detection (Check 4)

Detects BSN (Dutch 9-digit), IBAN, and credit card numbers (Luhn algorithm). Returns MODIFY or BLOCK per tenant configuration.

Phase 4b: Audio-Native Validation (Alternative Path)

POST /v1/validate-audio — Audio Validator

For voice calls where the platform sends raw audio instead of pre-transcribed text. The Audio Validator:

  1. Accepts base64-encoded PCM audio (16-bit LE, 16 kHz, mono).
  2. Performs ASR transcription (Deepgram, Whisper, or AssemblyAI — configurable per tenant via BYOK).
  3. Segments the audio using one of two strategies:
    • asr_first (default) — Streaming ASR drives sentence boundary detection.
    • prosodic_first — VAD silence detection creates segments, then batch ASR transcribes each.
  4. Runs the same 7-check pipeline on the transcription text (delegates to the Validator).
  5. Determines audio routing: forward (ALLOW), substitute (MODIFY — replaces with pre-generated TTS), or block.
  6. Returns per-segment results with dual hashes (audio_hash + text_hash) and a compliance receipt carrying an HMAC integrity tag.

Parallel ASR

When parallel_asr: true, the Audio Validator submits audio to multiple ASR models simultaneously, records all results in the evidence chain, and selects the highest-confidence transcription.

Request Shape

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
}

Response Shape

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: Mid-Call Events

POST /v1/events — The Evidence Layer accepts any event_type string. Common types:

Event TypeDescription
CALL_STARTMarks call beginning (auto-creates calls row)
VALIDATION_RESULTWritten by LLM Proxy after each /v1/validate call
AUDIO_VALIDATIONWritten by Audio Validator after each /v1/validate-audio call
CHECK_RESULTIndividual check outcome
FAIL_OPENValidator timeout event
DELIVERY_ATTESTATIONDelivery confirmation linked to validation event
TRANSCRIPT_RECONCILIATIONPost-call transcript comparison result
CALL_ENDMarks call completion
CALL_FAILEDMarks call failure

All events are appended to the cryptographic hash chain regardless of type. Custom event types (e.g. tool_call, consent_granted) can be sent freely.

Phase 5b: LLM Proxy

POST /v1/proxy/openai — Non-streaming compliance-aware LLM proxy. POST /v1/proxy/openai/stream — Streaming compliance-aware LLM proxy.

The LLM Proxy sits between the platform and the upstream LLM provider. For each request:

  1. Validates the request through the Validator.
  2. Forwards allowed requests to the upstream LLM (OpenAI-compatible API).
  3. Validates the LLM response before returning it to the platform.
  4. Writes evidence events for both request and response validation.
  5. Supports multi-candidate generation: sends the same prompt to N endpoints in parallel, validates all candidates, and returns the best compliant response.

Phase 6: Delivery Attestation

POST /v1/delivery-confirmation — Submit delivery confirmation.

After the validated response is delivered to the human interlocutor, the platform (or Audact's own audio monitor) sends a delivery confirmation. This creates a tamper-evident link between the validated content and what was actually delivered — any mismatch is detectable by recalculation against the stored commitment:

  • DC-1 (webhook): Platform sends a webhook with the delivered content hash.
  • DC-2 (api_query): Audact polls the platform API for delivery status.
  • DC-3 (tts_completion): TTS engine signals completion with content hash.
  • DC-4 (audio_monitoring): Audact's own ASR captures what was spoken (independent of the platform being audited).

Each confirmation includes:

  • linking_hash = SHA-256(validation_event_hash || confirmation_hash) — tamper-evident evidence chain link.
  • confirmation_grade: independent (DC-4), platform_sourced (DC-1/2/3), or unconfirmed (timeout).
  • content_match: whether delivered content matches validated content.

Delivery confirmations that don't arrive within 30 seconds are automatically marked unconfirmed.

GET /v1/calls/{call_id}/delivery — Get per-turn delivery attestation status for a call.

Phase 7: Call Termination

The platform sends a terminal event via POST /v1/events:

  • CALL_END — Sets calls.status = 'completed' and calls.ended_at atomically in the same transaction as the evidence event write.
  • CALL_FAILED — Sets calls.status = 'failed' and calls.ended_at atomically in the same transaction as the evidence event write.

Important: There is NO background sweep job. Calls that never receive a CALL_END or CALL_FAILED event remain as status = 'active' indefinitely. The platform is responsible for sending terminal events.

Phase 7b: C2PA Content Credentials

POST /v1/calls/{call_id}/c2pa — Generate C2PA manifest for a completed call.

After a call completes, tenants can request a C2PA-compatible content credential manifest. The manifest contains:

  • Claim: call metadata, evidence chain summary, config seal hash.
  • Assertions: per-event hash references (no raw event data), chain integrity proof.
  • Signature: HMAC-SHA256 over canonical JSON (RFC 8785) of claim + assertions using the tenant's KMS-managed key.
  • Merkle proof reference: if the call's evidence was included in a cross-tenant aggregation, the manifest references the branch proof and Merkle root.

The endpoint verifies call ownership before generating the manifest. HMAC key material is never included in the response.

Phase 7c: Cross-Tenant Aggregation

The Aggregator service periodically collects evidence hashes across all tenants and builds a Merkle tree for tamper detection and third-party auditability.

Aggregation Pipeline (automatic or manual)

POST /v1/admin/aggregate — Admin-only endpoint to manually trigger aggregation for a time window. Requires owner role. If no window is specified, aggregates the previous completed interval.

The pipeline:

  1. Collects per-tenant aggregate hashes (SHA-256 of all evidence hashes in the window).
  2. Builds a balanced binary Merkle tree with minimum 16-leaf padding.
  3. Requests an RFC 3161 timestamp token from a TSA for the Merkle root.
  4. Appends the root to the meta-chain (chain-of-chains).

Proof Retrieval (authenticated)

MethodPathDescription
GET/v1/proofsList aggregation windows in which the tenant participated (newest-first)
GET/v1/proofs/{window_id}Retrieve Merkle branch proof + RFC 3161 timestamp token for a specific window

Branch proofs are reconstructed on-demand from stored metadata. Proof steps contain only opaque sibling hashes and position — no tenant identifiers are leaked.

Public Verification (no authentication)

MethodPathDescription
GET/v1/metachainComplete meta-chain (chain-of-chains) of Merkle roots in ascending order
POST/v1/verify-inclusionOffline cryptographic verification of a Merkle branch proof

The meta-chain enables anyone to verify cross-aggregation integrity:

  • chain_hash = SHA-256(sequence || merkle_root || previous_hash)
  • Genesis entry: previous_hash = 64 zeros
  • The chain enforces integrity on every append

The verify-inclusion endpoint is fully offline — no database access, no authentication. Feed the branch_proof object from GET /v1/proofs/{window_id} directly into it.

Phase 8: Post-Call Verification

EndpointPurpose
GET /v1/verify/{call_id}Walks the evidence chain, recomputes SHA-256 hash for each event, verifies previous_hash links. Returns VALID or INVALID with broken_at_sequence.
GET /v1/chains/{call_id}Paginated evidence chain retrieval. Params: after_sequence, limit.
GET /v1/reconstruct/{call_id}Replays evidence chain to reconstruct the exact policy and validator configuration active during each event (config seal hash resolution).
GET /v1/receipts/{call_id}Retrieves and HMAC-verifies the compliance receipt for a call.

Transcript Reconciliation

POST /v1/transcript-reconciliation — Post-call transcript reconciliation.

After a call completes, the platform can submit the full post-call transcript for reconciliation against the evidence chain. The reconciler compares each transcript segment against validated output events:

  • MATCHED: Transcript segment hash matches validated output hash — confirmed delivery.
  • MISMATCHED: Transcript segment hash differs — a transcript_mismatch evidence event is generated.
  • UNVALIDATED: Transcript segment has no corresponding validation event — an unvalidated_content event is generated (detects platform insertions that bypassed Audact).

Audio Reconciliation

POST /v1/audio-reconciliation — Post-call audio reconciliation.

Combines ASR transcription and transcript reconciliation in a single synchronous endpoint. The caller sends the full call recording; the service transcribes it via ASR and then reconciles the transcription against the evidence chain. This is the preferred endpoint when the platform has raw audio but no text transcript.

This is a synchronous endpoint — the caller blocks until transcription and reconciliation are complete. For long recordings this may take several seconds.

Request Shape

json

{
  "call_id": "uuid",
  "audio_data": "base64-encoded audio bytes",
  "audio_format": "pcm_s16le_16000 | wav | mp3",
  "language": "nl",
  "asr_provider": "deepgram"
}
  • audio_data: Standard base64-encoded audio. Body limit is 64 MiB (~34 min PCM at 16 kHz mono).
  • audio_format: One of pcm_s16le_16000 (raw PCM 16-bit LE, 16 kHz, mono), wav, or mp3.
  • language: BCP-47 tag (optional, defaults to nl).
  • asr_provider: Override the default ASR provider (optional).
  • No tenant_id field — tenant identity comes from auth context.

Response Shape

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"
    }
  ]
}

Flow

  1. Authenticate via API key.
  2. Verify call ownership.
  3. Decode base64 audio bytes.
  4. Transcribe full audio via ASR (same ASR client as /v1/validate-audio).
  5. Build reconciliation segments from ASR output.
  6. Reconcile against the evidence chain (same reconciler as /v1/transcript-reconciliation).
  7. Return segment-by-segment results.

Tenant Management

These endpoints manage tenants, API keys, and configurations. They are not part of the per-call flow but are required for setup.

Tenants

MethodPathRoleDescription
POST/v1/tenantsPublic (rate-limited)Create a new tenant
GET/v1/tenants/{tenant_id}ViewerGet tenant details
PUT/v1/tenants/{tenant_id}OwnerUpdate tenant

API Keys

MethodPathRoleDescription
POST/v1/tenants/{tenant_id}/keysOwnerGenerate a new API key
GET/v1/tenants/{tenant_id}/keysDeveloperList API keys (prefix only)
DELETE/v1/tenants/{tenant_id}/keys/{key_id}OwnerRevoke an API key

Configurations

MethodPathRoleDescription
POST/v1/configurationsDeveloperCreate configuration
GET/v1/configurations/{config_id}ViewerGet configuration
PUT/v1/configurations/{config_id}DeveloperUpdate configuration
DELETE/v1/configurations/{config_id}OwnerSoft-delete configuration
GET/v1/tenants/{tenant_id}/configurationsViewerList tenant configurations

Configurations include:

  • Policy rules (blocklist patterns, PII detection, pricing bands, escalation keywords, consent gates)
  • Multi-candidate LLM endpoints (BYOK: URL + API key per provider, stored securely)
  • Output constraints (response length limits with compliance-critical content preservation)
  • ASR provider keys (BYOK: Deepgram, Whisper, AssemblyAI — API keys stored securely)

Authentication

MethodPathDescription
POST/v1/auth/exchange-keyExchange API key for JWT access + refresh tokens
POST/v1/auth/refreshRefresh an expired JWT using the refresh token cookie

Usage

MethodPathRoleDescription
GET/v1/tenants/{tenant_id}/usageViewerGet tenant API usage statistics

Complete Endpoint Summary

MethodPathDescription
POST/v1/evaluate"May this call happen?" (pre-dial, stateless)
GET/v1/policies/{country}Get country policy ruleset
POST/v1/validate"May the AI say this?" (per turn, 40ms deadline)
GET/v1/receipts/{call_id}Retrieve compliance receipt (post-call)
POST/v1/eventsAppend event to chain (during call)
GET/v1/callsList calls (filtered by tenant)
GET/v1/chains/{call_id}Retrieve event chain (post-call, paginated)
GET/v1/verify/{call_id}Chain integrity check (post-call)
GET/v1/reconstruct/{call_id}Rebuild config history (post-call / audit)
POST/v1/delivery-confirmationSubmit delivery confirmation
GET/v1/calls/{call_id}/deliveryGet delivery attestation status
POST/v1/transcript-reconciliationPost-call transcript reconciliation
POST/v1/proxy/openaiCompliance-aware LLM proxy (non-streaming)
POST/v1/proxy/openai/streamCompliance-aware LLM proxy (streaming)
POST/v1/tenantsCreate tenant
GET/v1/tenants/{tenant_id}Get tenant
PUT/v1/tenants/{tenant_id}Update tenant
POST/v1/tenants/{tenant_id}/keysGenerate API key
GET/v1/tenants/{tenant_id}/keysList API keys
DELETE/v1/tenants/{tenant_id}/keys/{key_id}Revoke API key
POST/v1/configurationsCreate configuration
GET/v1/configurations/{config_id}Get configuration
PUT/v1/configurations/{config_id}Update configuration
DELETE/v1/configurations/{config_id}Soft-delete configuration
GET/v1/tenants/{tenant_id}/configurationsList configurations
GET/v1/tenants/{tenant_id}/usageGet usage statistics
POST/v1/auth/exchange-keyExchange API key for JWT
POST/v1/auth/refreshRefresh JWT
POST/v1/validate-audioValidate audio segment (ASR + compliance)
POST/v1/audio-reconciliationPost-call audio reconciliation (ASR + reconciliation, synchronous)
POST/v1/calls/{call_id}/c2paGenerate C2PA content credential manifest
POST/v1/admin/aggregateTrigger manual aggregation (owner only)
GET/v1/proofsList aggregation windows for tenant
GET/v1/proofs/{window_id}Get Merkle branch proof + RFC 3161 token
GET/v1/metachainGet meta-chain of Merkle roots (public)
POST/v1/verify-inclusionVerify Merkle inclusion proof (public)
GET/v1/healthHealth check

Evidence Properties

All evidence events are:

  • Append-only — No updates, no deletes
  • Cryptographically chained — SHA-256 over pipe-delimited pre-image: event_id|call_id|sequence|event_type|timestamp|canonicalJSON(data)|previous_hash
  • Server-timestamped — time.Now().UTC(), never from client
  • Write-protected — Concurrent writes on the same call_id are serialised
  • Config-bound — Each event includes config_seal_hash (SHA-256 of RFC 8785 canonical JSON of tenant config)

The call state cache is derived from the evidence chain, not independent. On cache miss: replay events from the store, reconstruct state, refresh cache.

Security Model

  • Tenant isolation: Every request is authenticated via API key → tenant identity extracted from auth context. No tenant_id in request bodies.
  • Call ownership: call_id verified to belong to authenticated tenant before processing.
  • Evidence integrity: Chain columns are append-only; any tampering is detectable by recalculation against stored commitments.
  • Secret management: LLM and ASR API keys stored securely; keys redacted to has_key: true in GET responses.
  • No PII in logs: Transcripts, phone numbers, and audio content never logged. Only hashes and metadata in structured logs.

Source updated: October 19, 2026