Skip to content

Recording transcripts, audio redaction and the screen-monitoring notice ​

Spanish version: Transcripciones, redacción y grabación de pantalla.

This guide covers two things the platform does with a recorded call and the routes that expose them: the post-call transcript and the redaction of personal data in the audio. Further sections give the contract of the screen-monitoring notice routes and of the screen recording and live screen-monitor session routes. Field names, enums and status codes are copied from the vendored OpenAPI document (core v0.105.0); the API Reference generated from it is the authority on shapes.

Availability ​

CapabilityAvailability
GET /v1/recordings/{id}/transcriptPublished in core v0.103.0 (core#1971, core#2064)
GET / POST /v1/recordings/{id}/redaction, POST /v1/recordings/{id}/original-urlPublished in core v0.103.0 (core#2016)
GET /v1/screen-monitor/notice, POST / GET /v1/screen-monitor/acksPublished in core v0.103.0 (core#1989)
PUT /v1/screen-recordings/{id}/chunks/{n}, POST /v1/screen-recordings/gapsFirst in core v0.104.0 (core#2036); present in the v0.105.0 spec
PUT /v1/screen-monitor/armed, POST /v1/screen-monitor/sessions, POST .../sessions/{id}/signal, POST .../sessions/{id}/endFirst in core v0.104.0 (core#1990); present in the v0.105.0 spec
GET /v1/screen-monitor/sessionsFirst in core v0.105.0 (core#1990)

Every screen-monitor and screen-recordings route answers 404 not_found while the tenant's screen_monitor_enabled flag is off, except the ledger reads (GET /v1/screen-monitor/acks, GET /v1/screen-monitor/sessions) and POST .../sessions/{id}/end, which the spec states do not consult it.

Whether a given recording has a transcript depends on eligibility (see below) and on the cell having a transcription engine configured. A recording without one is 404, not an empty document.

Transcript ​

http
GET /v1/recordings/{id}/transcript

The 200 body is { "transcript": { ... } } with the fields of the RecordingTranscript schema:

FieldTypeMeaning
recording_iduuidThe recording.
providerstringSpeech-to-text provider that produced it.
modelstringModel that produced it.
languagestring or nullLanguage the provider reported, when it reported one.
audio_secondsintegerSeconds of audio billed.
segment_countintegerNumber of timed segments.
created_atdate-timeWhen the transcript was stored.
segmentsarrayTimed segments, ordered by seq.

Each entry of segments has seq (integer from 0), channel (audio channel, 0 or 1), start_ms, end_ms and text.

Who can read it ​

A transcript is the raw personal data of a call, so the route is narrow:

  • Allowed: a dashboard login (ddu_) with a supervisor or admin seat, or an account_admin login without a seat.
  • Not allowed: a dd_ tenant API key, a widget token and an agent seat all get 403 role_forbidden.

What is audited ​

Every delivery appends one read event in the same transaction as the read: who read it, when, which recording and how many segments. The event never holds the text. If the event cannot be written, no text is returned and the answer is 503; retry later.

When it is 404 ​

404 is one answer for several situations, so the route cannot be used to probe other tenants: the recording belongs to another tenant, its audio was purged (the purge deletes the transcript with it), it was never eligible for a transcript, or the id is malformed. A recording under legal_hold keeps its transcript.

Status codes ​

CodeMeaning
200The transcript.
401Missing or invalid credentials.
403The caller's role cannot read transcripts.
404No transcript for this recording in this tenant.
503The read event could not be recorded, so no text was returned.

If the transcription provider is not available ​

Transcription runs after the call, outside the request path, and it fails closed:

  • Paid transcription spend is capped per month by the platform's spend brake. A paid provider is only used when the cap is positive; with a cap of 0 the job stops without spending and does not fall back to a local engine silently.
  • If the transcription engine refuses the requested provider, the job ends without storing anything and the reserved spend is released. If it answers with a different provider than the one reserved, core does not store that answer either.
  • Eligibility is decided before any spend: the plan tier must include it (Scale and Enterprise), the recording must be available, within the duration limits, answered by a person and not an AI call, and without a previous transcript. What does not qualify is not reserved, not billed and not queued again.

The visible effect for an API client is the same in every case: the recording has no transcript and GET .../transcript answers 404. There is no "pending" state in the response.

Audio redaction ​

Redaction never edits the stored recording. The platform keeps the original untouched, with its retention and legal hold, and builds a derived copy with the sensitive spans silenced or replaced by a tone. That copy is the default view.

What gets redacted ​

A span is a time range plus a category from a closed list. The request carries no value, and the stored redaction holds none either:

FieldTypeNotes
start_msinteger, min 0Start of the span.
end_msintegerEnd of the span.
categoryenumcard, ssn, bank_account, pin.

Only WAV recordings can be redacted. Any other format is 422 unsupported_codec.

Ask for a redaction ​

http
POST /v1/recordings/{id}/redaction
json
{
  "method": "silence",
  "segments": [
    { "start_ms": 12400, "end_ms": 15800, "category": "card" }
  ]
}
  • segments is required: 1 to 500 entries.
  • method is silence (default) or beep.
  • The spans are unioned with those already stored, and the copy is rebuilt from the original. The call is idempotent; version goes up only when something new was added.
  • The caller needs the same role as for the original (see below).

The 200 body is { "redaction": { ... } } with recording_id, method, segments, redacted_ms, version, codec, requested_by, created_at and updated_at. Status codes: 400, 401, 403, 404, 409, 422.

Read the redaction ​

http
GET /v1/recordings/{id}/redaction

Returns the same redaction object: times, categories, method and version, never the values and never object-store keys. 404 when the recording has no redaction. Status codes: 200, 401, 403, 404.

Playback and the original ​

POST /v1/recordings/{id}/playback-url returns the redacted copy when the recording has one. If the redaction row exists but its copy is gone (purged), the route does not fall back to the original.

The unredacted audio has its own door:

http
POST /v1/recordings/{id}/original-url
json
{ "reason": "Dispute 2026-0417: customer denies the payment promise" }
  • Who can ask: a dd_ tenant key or a dashboard login with the account_admin role. Anything else is 403 role_forbidden.
  • reason is required, non-empty, at most 500 characters.
  • The actor and the reason are written to an append-only access log before the URL is returned; if the log entry cannot be written, no URL is issued.
  • The 200 body is a signed, short-lived URL (url, expires_at). Status codes: 400, 401, 403, 404, 409.

Screen-monitoring notice and acknowledgements ​

Before a seat's screen may be observed, the seat acknowledges a versioned notice. This section is the contract of the routes only. The legal text of the notice is not published in this guide; see Not covered here.

Read the current notice ​

http
GET /v1/screen-monitor/notice

The 200 body is { "notice": { ... } } with version, lang, title, body, text_sha256 (SHA-256 hex of body in UTF-8), effective_at and published_at. The notice is a global, append-only catalogue: publishing a newer version invalidates every acknowledgement of the older ones without touching them.

  • Reachable by a dd_ key and by a ddu_ login; a ddw_ widget token is 403.
  • 404 not_found when the tenant's screen_monitor_enabled flag is off, and also when no version has been published yet.
  • Status codes: 200, 401, 403, 404.

Acknowledge it from one's own seat ​

http
POST /v1/screen-monitor/acks
json
{ "notice_version": 3, "scope": "live_monitoring" }
FieldTypeNotes
notice_versioninteger, min 1Required. Must be the current version.
scopeenumRequired: live_monitoring or screen_recording.
agent_iduuidOptional. If present it must be the caller's own seat.
  • The two scopes are acknowledged separately: accepting live monitoring does not authorize recording, and a new notice version invalidates both.
  • Only a ddu_ login with a linked seat can acknowledge. A dd_ key is 403 forbidden; a ddu_ with no active seat is 403 seat_not_linked; an agent_id that is not the caller's own seat is 403 forbidden.
  • The seat comes from the bearer, never from the body.
  • Repeating the same version and scope returns the first acknowledgement (200, no new row). The ledger is append-only.
  • 404 not_found while screen_monitor_enabled is off. With the flag on: 409 no_notice_published when no version exists, 409 stale_notice when notice_version is not the current one.
  • The 200 body is { "ack": { ... } } with id, agent_id, notice_version, scope, text_sha256 (copied from the notice the seat saw), channel (dashboard_checkin) and acked_at. Status codes: 200, 400, 401, 403, 404, 409.

List acknowledgements ​

http
GET /v1/screen-monitor/acks?limit=50&cursor=...&agent_id=...

Newest first, paginated by cursor: limit 1 to 200 (default 50; a larger value is capped, not refused), cursor is the next_cursor of the previous page and agent_id filters to one seat. The body is { "acks": [...], "next_cursor": ... } and next_cursor is null on the last page.

Allowed only to a seat whose template role is supervisor or admin (403 role_forbidden otherwise; a dd_ key and a ddu_ with no seat are 403). It does not depend on the screen_monitor_enabled flag: it is the record of what was accepted, and turning the function off neither deletes nor hides it. Nothing crosses tenants. Status codes: 200, 400, 401, 403.

Screen recording ​

The agent's browser captures its own screen and uploads it to core in chunks. All of this is behind the tenant flag screen_monitor_enabled (see the note under Availability) and behind a separate acknowledgement.

Recording needs a current acknowledgement with scope: "screen_recording" (see Acknowledge it from one's own seat). It is separate from live_monitoring: accepting one never authorizes the other, and a new notice version invalidates both. GET /v1/me reports it per scope in screen_monitor.scopes.<scope>.ack_current (ScreenMonitorStatus); a scope is authorized only when enabled is true and ack_current is true.

Upload a chunk ​

http
PUT /v1/screen-recordings/{id}/chunks/{n}?call_uuid=...
Content-Type: video/webm
  • id is a UUID the client chooses, one per call, so a retry is idempotent. n is the chunk number, from 0 to 1,000,000. call_uuid is required (1 to 128 characters) and must be a call of the caller's own seat.
  • The body is video/webm or video/mp4, at most 4,000,000 bytes per chunk (413 above that, 415 for another type).
  • Only a ddu_ login with a linked seat may upload; a dd_ key, a ddu_ with no seat and a ddw_ token are 403.
  • The seat, the call and the retention come from core, not from the client. The chunk is sealed with the tenant's key (AES-256-GCM) before it reaches the store, bound to id and n.
  • Without a current screen_recording acknowledgement the answer is 403 screen_recording_not_acknowledged, nothing is stored and a not_armed gap is recorded.

201 means the chunk was sealed and stored; 200 means the same bytes were already stored (status: duplicate). The body is { "chunk": { ... } } with recording_id, seq, bytes (size before sealing), status (stored or duplicate) and retained_until (a date, inherited from the voice recording of the same call).

CodeMeaning
404Flag off, or call_uuid unknown or another agent's call (same answer for both).
409 chunk_conflictThat n already holds other bytes.
409 recording_conflictThe call already has a recording under another id, or id belongs to another call.
410The screen recording was purged at the end of its retention.
503The store did not take the chunk; retry it. An upload_failed gap is recorded.

Other codes: 400, 401, 403, 413, 415.

Declare a gap ​

http
POST /v1/screen-recordings/gaps
json
{ "call_uuid": "...", "reason": "agent_stopped", "note": "capture ended by the agent" }

reason is not_armed, agent_stopped or upload_failed; note is optional, at most 500 characters. It is idempotent per call, reason and source, needs no acknowledgement (a gap is evidence, not content) and applies the same seat and call rules as the upload. The 200 body is { "gap": { "call_uuid", "reason", "source" } } with source always agent for this route; core writes its own not_armed gap when a chunk arrives without an acknowledgement.

Retention, reads and purge ​

retained_until in the chunk response is inherited from the voice recording of the same call, and an upload to a recording purged at the end of its retention is 410. The vendored v0.105.0 spec does not publish a route to read a screen recording back, a signed URL for it, or a purge route, so this guide does not describe them.

Live screen-monitor sessions ​

A supervisor can watch an agent's screen live. Core does not see the video: it validates, writes a ledger row and signals both browsers, which negotiate the media peer to peer.

  1. Arm. PUT /v1/screen-monitor/armed with { "armed": true, "tab_id": "..." } (tab_id 1 to 64 characters) from the agent's own seat once its dashboard holds a capture. Arming needs the seat to be checked in (409 not_checked_in) and to hold a current live_monitoring acknowledgement (409 no_ack). One tab per seat: arming with a different tab_id displaces the previous one. The armed state lives in the process, so a restart disarms everyone.
  2. Open. POST /v1/screen-monitor/sessions with { "agent_id": "..." } from a supervisor or admin seat. 201 returns { "session": { ... } } with result: "started". Every outcome writes a ledger row, refused ones included. 409 codes: no_ack, not_armed, agent_busy, agent_unreachable. An unknown agent, an agent of another tenant and an inactive agent are the same 404.
  3. Signal. POST /v1/screen-monitor/sessions/{id}/signal with kind (offer, answer or candidate) and payload. offer only from the agent, answer only from the supervisor. The payload is forwarded and never stored; at most 16,384 bytes (413 payload_too_large) and 60 messages per 10 seconds per session (429 rate_limited with Retry-After). 202 returns delivered, false when the other party had no live socket. A closed session is 409 session_closed.
  4. End. POST /v1/screen-monitor/sessions/{id}/end, optional body { "reason": "peer_failed" }. Either party may call it, it is idempotent, and it works with the flag off. The system also closes a live session on checkout, lost socket, lapsed credential, or when the flag, the supervisor's role or the agent's acknowledgement stop holding.

GET /v1/screen-monitor/sessions lists the ledger (supervisor or admin seat, newest first, limit 1 to 200 default 50, cursor, agent_id, and result in started, ended, failed, refused). A row (ScreenMonitorSession) has id, supervisor_user_id, supervisor_id, agent_id, notice_version, result, reason (flag_off, role, agent_not_found, no_ack, not_armed, agent_busy, agent_stopped, credential_lapsed, checkout, peer_failed or null), started_at and ended_at. It never carries signalling payload and does not depend on the flag.

Who can change the compliance configuration ​

Writing the tenant's compliance configuration takes an administrator: a dd_ key or an account_admin login (core#2438). A member login gets 403 role_forbidden on every write. In this guide's neighbourhood that is:

  • PUT /v1/recordings/{id}/legal-hold, to place or lift a legal hold.
  • PUT /v1/recording-policy, the tenant recording policy.

Reading does not change: GET /v1/recording-policy and the recording's own metadata, where the hold shows, stay open to the same readers as before. The same rule covers the artificial-voice disclosure, the retry rules and the AI pause and resume; those routes are described in the generated API reference, not in this guide.

Not covered here ​

Redaction of personal data in the screen recording (core#2017) is not published in the v0.105.0 spec, so this guide makes no claim about it.

The legal text of the monitoring notice and the jurisdiction-by-jurisdiction legal matrix are not published in this guide. They wait on the decision tracked in macro-tasks#415, and the Roadmap lists them as not served. The routes above only carry whatever notice text the platform publishes; they do not make a claim about what the law requires.

How spans are detected is not part of the redaction routes: the endpoint receives spans, it does not find them. Redaction of Opus audio is not served; only WAV is, as the 422 above states.

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.