Compliance archive cookbook - recordings, transcripts, CDR.
Three tenant-scoped archive surfaces exposed through signal.ashx: call recordings (WAV + JSON sidecar), AI transcripts (JSON with _transcriptTurns), and CDR (Call Detail Records, CSV or JSON). Every endpoint is bearer-authenticated (ak_ API key or admin OIDC), tenant-scoped by request Host, and honestly framed under NIS2 and DORA archive-integrity duties. European Digital Identity Wallet-enabled tenants use the same auth surface.
Task A - list and fetch call recordings
Endpoints verified in live code: GET /signal.ashx?recordings-list=1 returns {ts, tenant, rootDisplay, rootExists, countAll, countReturned, totalBytes, items[]}, then GET /signal.ashx?recording=<callId>&download=1 for the WAV byte stream. Auth: Authorization: Bearer ak_... or admin OIDC bearer.
| Language | Library | Recipe | Snippet |
|---|---|---|---|
| cURL | plain shell | Open | fetch-recordings-curl.sh |
| Node.js | axios / ws | Open | fetch-recordings-nodejs.js |
| Python | requests / websockets | Open | fetch-recordings-python.py |
| PHP | curl | Open | fetch-recordings-php.php |
| .NET | HttpClient | Open | fetch-recordings-dotnet.cs |
Recording fetch - cURL Recipe
Recording fetch - Node.js Recipe
Recording fetch - Python Recipe
Recording fetch - PHP Recipe
Recording fetch - .NET Recipe
Task B - list and fetch AI transcripts
Endpoints verified in live code: GET /signal.ashx?list-transcripts=1 returns {count, errors, dirs[], items[]}, then GET /signal.ashx?get-transcript=<basename>.json for the full JSON file including the _transcriptTurns array (written by GeminiTranscriberTap). Auth model: Bearer required (admin OIDC or ak_ API key). The HMAC header is not accepted at this endpoint today.
| Language | Library | Recipe | Snippet |
|---|---|---|---|
| cURL | plain shell | Open | fetch-transcripts-curl.sh |
| Node.js | axios / ws | Open | fetch-transcripts-nodejs.js |
| Python | requests / websockets | Open | fetch-transcripts-python.py |
| PHP | curl | Open | fetch-transcripts-php.php |
| .NET | HttpClient | Open | fetch-transcripts-dotnet.cs |
Transcript fetch - cURL Recipe
Transcript fetch - Node.js Recipe
Transcript fetch - Python Recipe
Transcript fetch - PHP Recipe
Transcript fetch - .NET Recipe
Task C - export CDR (Call Detail Records)
Endpoint verified in live code: GET /signal.ashx?cdrlog=1 with optional filters direction=in|out|transit, trunk=, date=YYYY-MM-DD, limit=N, q=. Response: JSON {events[], count, trunks[], dates[]}. Add format=csv for the same rows as CSV with columns recordedUtc, startUtc, endUtc, direction, trunk, callingNumber, calledNumber, durationSec, answered, outcome, releaseReason. Ideal for BI ingest.
| Language | Library | Recipe | Snippet |
|---|---|---|---|
| cURL | plain shell | Open | export-cdr-curl.sh |
| Node.js | axios / ws | Open | export-cdr-nodejs.js |
| Python | requests / websockets | Open | export-cdr-python.py |
| PHP | curl | Open | export-cdr-php.php |
| .NET | HttpClient | Open | export-cdr-dotnet.cs |
CDR export - cURL Recipe
CDR export - Node.js Recipe
CDR export - Python Recipe
CDR export - PHP Recipe
CDR export - .NET Recipe
Auth model
All three endpoints accept an Authorization: Bearer <token> where the token is either an ak_-prefixed API key (see the M2M cookbook) or an admin OIDC access token. The recordings and CDR endpoints additionally accept an HMAC signature header X-CodeB-Admin-Signature for legacy internal jobs. The transcripts endpoint is Bearer-only.
Share-link model
An admin can create an anonymous, read-only, time-limited share for a single recording (create-recording-share) or a single transcript (create-transcript-share). The returned token becomes a public URL under recording-shares.html or transcript-shares.html. The share stream endpoint applies anti-download headers; the WAV audio plays inline. Shares are revocable in one click from the shares admin page.
Retention model
Retention is per-tenant, driven by Recording:RetentionDays in the bridge appsettings. The bridge default is 90 days; individual tenants can raise or lower the window. The sweeper runs on the bridge process, not in signal.ashx.
Legal framing - NIS2 / DORA / CRA
Recordings, transcripts and CDR support obligations across NIS2 (secure log preservation), DORA (financial-services incident evidence) and the EU Cyber Resilience Act (product-side auditability). The archive layer is designed for tenant isolation and integrity, but we are explicit about limits:
- No cryptographic timestamping today. Recordings and transcripts land on disk with a JSON sidecar; if you need RFC 3161 or LTV, apply it externally at archive time.
- No WORM storage tier today. Retention is time-based (see below), not immutable.
- Cross-tenant leaks are actively guarded. Requests are keyed by the tenant Host header; recording sidecars carry a
tenantfield and mismatches are logged and skipped.
RASP posture
Every archive endpoint runs inline anomaly detection + integrity checks with loud [api-key-diag], [REC-*], [TRANSCRIBE-*] and [cdr-*] diagnostics. Per-tenant Host-header binding is enforced on every request; a recording sidecar carrying a different tenant field is skipped and logged. Filename inputs use a strict [A-Za-z0-9._-] allowlist to block path traversal.
FAQ
Structured answers are embedded in the schema block above.