Set it
null to keep that kind for good. The PUT replaces all three, so a field you leave out goes back to being kept for good. The project read (GET /v1/projects/{id}) shows the setting under retention.
An agent can override any of the three for its calls, in its config:
null takes the project’s. The override is read from the agent’s live version (the published one, or the newest if none is published) and applies to the calls of all its versions, so publishing a new retention covers the agent’s earlier calls too. After a handoff, the agent the call ended with decides. An inline agent from call-init has no stored version, and its calls take the project’s retention.
Changing an agent’s retention needs an admin key too, since it overrides the project’s. A developer key can create, update, publish, roll back and delete agents as before, and can carry an agent’s retention over unchanged. But a request that writes a different retention than the one that applies to the agent, or that makes a different one apply (publishing or rolling back to a version with another retention), is refused with 403 and code: "forbidden", and the change is not made. The error’s details give the agent’s current retention and the requested one.
What each kind covers
- Recordings: the call’s audio in your object storage (local or S3), the copy on your Twilio account if it recorded the call too (see below), and
recording_urlon the call.recording_statusbecomesexpired, and the call’srecording.readyevent loses its URLs:recording_url,twilio_recording_urland any other*_urlfield are set tonull. - Transcripts: what was said and what was made from it. The
transcript.finallines, thetool.resultevents and the call’s tool invocations (a tool’s arguments and result come from the conversation),context.injectedmessages, and the post-call analysis, which includes the summary,structured_dataand takeaways. Other events can quote the conversation too, so every field of theirs that holds free text is set tonullat the same time: acall.agent_handoff’sreasonandcontext_payload, acall.transferred’sreasonandtransfer_message, the digits ofdtmf.sent, and the error text oferror.raisedandprovider.failover. These are the fields redaction analyses. The events themselves stay until the events kind expires, becauseended_reasondepends on them. - Events: every other event on the call’s event log: lifecycle and carrier events,
metrics.turn,provider.failover,error.raisedand the rest.recording.readyis the exception: it names the recording, so it stays as long as the recording does, and is deleted with the recording if the other events went first.
What stays
The call record itself is never deleted: its id, numbers, direction, status, times, duration, agent andmetadata. recording_status stays too, as expired once the recording is deleted (a call that never had a recording keeps none or failed). Three fields on GET /v1/calls/{id} say what was deleted and when: recording_expired_at, transcript_expired_at and events_expired_at.
After a kind expires, its reads return nothing for it:
ended_reason is worked out from the events, so it is saved on the call before they are deleted, and the read keeps saying how the call ended.
In call.ended
Every call.ended webhook states the retention that applies to the call, with the agent’s override already applied:
null means kept for good. It is the policy when the call ended; a later change applies to stored calls too.
When it runs
Each server runs the janitor once an hour, so data is deleted within the hour after it expires. Several servers can run it at once: each takes the calls it deletes, and the others skip them. A change applies to calls already stored. Shortening a retention deletes what is now past it on the next run; lengthening it keeps what has not been deleted yet, but nothing already deleted comes back. A call with no recorded end (a server that died mid-call) is counted from its start. A recording object that cannot be deleted (storage unreachable) is logged asretention_recording_delete_failed, and a Twilio copy that cannot be deleted as retention_carrier_recording_delete_failed. Either way the call is tried again on the next run, so a call is never marked expired with its recording still in storage or on your Twilio account.
A run that re-analyses a call (POST /v1/calls/{id}/analysis/rerun) while its transcript expires stores nothing and sends no call.ended.
What it does not touch
- The audit log. It has its own retention; call retention never deletes from it.
- Text conversations (SMS, WhatsApp, the Chat API) are not covered yet.
- A carrier copy TurnCall does not know about. TurnCall records calls itself, in your storage. If your Twilio account also records them and its recording callback reaches TurnCall, the
recording.readyevent names that recording, and TurnCall deletes it from the Twilio account (the number’s own, or the platform’s) when the recording expires. A recording whose callback never reached TurnCall, or one on an account TurnCall holds no credentials for (logged asretention_carrier_recording_unreachable), it cannot delete. Your carrier may keep its own copy: delete it there, or turn carrier recording storage off. - Logs, traces and webhook deliveries that already left the server. Redact them with redaction.