Skip to main content

When to use this

PII Redaction runs after ingestion: you send the call with redact_fields, and Cekura detects and removes the entities from the stored transcript and recording. Client-side redaction runs before ingestion: detection and removal happen in your own process, on your own LLM and speech-to-text providers, with your own API keys. The sensitive values never reach Cekura at all. Use it when:
  • Your compliance posture requires that raw PII never leaves your network.
  • Your organization is required to use specific model providers or regions for any system that processes customer data.
  • You need to scrub fields that server-side redaction does not cover. redact_fields applies to the transcript and the audio recording; metadata, dynamic_variables and customer_number are stored as you send them.
The two are complementary. Client-side redaction costs you nothing in Cekura credits but requires you to run the detection; server-side redaction is one parameter and costs 0.4 credits per minute. See Combining both.

How it works

The script covers the transcript and the recording, and detects entities automatically — you do not have to know the sensitive values in advance.
1

Detect

The transcript is sent to your LLM with a detection prompt, which returns every PII span it found as {original_text, replacement_token}. Detection is context-driven rather than pattern-driven, so it catches what a regex cannot: dictated digits ("four one five, five five five"), spelled-out names ("is that spelled b e n?"), and spoken emails ("a at gmail dot com").
2

Scrub the transcript

Each span is replaced with the same placeholder token Cekura’s server-side redaction emits (<PERSON>, <EMAIL>, <PHONE>, …), applied per JSON string leaf so roles, timings and tool-call IDs survive intact.
3

Beep the recording

The audio is transcribed with word-level timestamps by your STT provider, PII is detected on that transcription, each span is mapped back to its time range in the waveform, and those ranges are replaced with a tone.
Redaction fails closed. If detection fails, or a detected span cannot be aligned to the audio, the script raises RedactionError instead of returning content. Never catch that error and upload the original — that is the one mistake in this whole flow that silently ships raw PII.

What you need

The script

Using it

Scrub the payload and the recording, then send both. The spans found on the transcript are passed into the audio pass as supplemental_spans: each pass detects on its own text, so the union beeps entities either one alone would have missed.
If you host the redacted recording yourself, send voice_recording_url in a JSON body instead of uploading voice_recording as multipart — the payload is otherwise identical.
Transcript before:
Transcript after:
The recording gets a tone over 0:04.2–0:09.6 — the span where those words were spoken — and is otherwise byte-for-byte the same length, so transcript timings still line up with the audio in the Cekura player.

Bringing your own providers

detector and transcriber are plain callables. The two adapters in the script cover OpenAI-compatible endpoints; anything else is a short function with the same contract.
Takes the prompt string, returns a list of {"original_text": ..., "replacement_token": ...} — or None if the call failed. Returning None is what makes the script fail closed, so never return [] on an error: an empty list means “this transcript contains no PII”.
Use a tool call or structured-output mode rather than free-form text — the spans must come back parseable, and original_text must be verbatim or the replacement silently misses.
Takes the audio bytes, returns (full_text, words) where each word is {"text": ..., "start": <seconds>, "end": <seconds>}. Most STT APIs return this shape under a different key:
Whatever provider you use, confirm word timings are in seconds — some APIs return milliseconds or nanoseconds, and a unit mismatch beeps the wrong part of the call without any error.
Detection is a long-context extraction task with a strict verbatim-copy requirement, not a reasoning task. A mid-tier instruction-following model with reliable tool calling is the right trade-off; run it at temperature=0.Do not cap output tokens tightly. The prompt asks for one entry per occurrence, so a long call with repeated PII produces a long span list, and a truncated tool call fails to parse — which correctly fails closed, but wastes the run.

Optional: pinning values you already know

Detection is automatic, so known_values is optional. Pass it when a specific value must never survive — the caller’s name and address from your CRM, say — and those literals are scrubbed deterministically on top of whatever the model found:
The script also runs a small deterministic sweep of its own for the unambiguous cases — currency amounts, month-and-day dates, well-formed emails, IPs, SSNs and phone numbers — so a value the model misreads as something else is still caught.

Combining both

Fully client-side

Scrub the payload and beep the recording yourself, and send neither redact_fields nor raw audio. Nothing unredacted ever leaves your network, and no redaction credits are charged.

Client-side transcript, server-side audio

Scrub the JSON yourself — including the metadata and dynamic_variables that redact_fields does not reach — and let Cekura redact the recording on ingest by sending redact_fields.
Client-side transcript scrub + server-side audio redaction
Sending redact_fields costs 0.4 credits per minute regardless of how many fields are listed. If your client-side pass already covers you and you are not sending raw audio, omit redact_fields entirely.

Limits

  • Detection quality is your model’s. The prompt in the script is the same approach Cekura’s own redaction uses, but the result depends on the model behind it. Evaluate on your own calls before trusting it in production.
  • Audio alignment needs an accurate transcription. A span the STT transcribed differently cannot be aligned, and the script fails closed rather than shipping partially redacted audio. If that happens often, the fix is a better STT model, not looser alignment.
  • Two providers, two failure modes. Detection and transcription are network calls in your ingestion path. Give them generous timeouts and decide up front what you do with a call whose redaction failed — the safe default is to skip sending it.
  • Redaction is irreversible. Cekura only ever sees the tokens and the beeps, so a redacted value cannot be recovered later for debugging. Keep your own mapping if you need one.
  • Broad fields cost context. Enabling numerical_pii tokenizes order and confirmation numbers too, which can make metrics about those flows harder to evaluate. Enable the narrowest set that satisfies your policy.

Verifying before you roll out

Run the script over a batch of real calls and inspect what changed before putting it in the ingestion path:
Read the span list, not just the output: a short list on a call you know contains PII means detection under-fired, and that is the failure worth catching before it reaches production. Then send a handful of scrubbed calls to a throwaway agent and confirm the transcripts read the way you expect, the recording plays with tones in the right places, and your metrics still evaluate against them.