What is recorded
A request that changes several things writes an entry for each, and every one of them carries the same
request_id. Publishing version 2 of an agent whose version 1 is live writes three: version 2 published, version 1 archived, and the number that pointed at version 1 updated.
Every successful request on these routes writes at least one entry, even one that changed nothing: deleting a credential that is not there writes a deleted entry with no changes. A request that fails writes none.
The project and its first API key are created with the platform credential (X-Platform-Key), not an API key. Their entries have actor: "platform" and api_key_id: null. Every other entry has actor: "api_key" and the key’s id.
What changed
changes maps each field that changed to its value before and after, as the API reads it:
/config/llm/model, /limits/live_calls, /config/tools/0/webhook_secret): one / per level, a list item by its index. A ~ or / inside a field’s own name is written ~0 or ~1, so a metadata key such as team.name or a/b is /metadata/team.name or /metadata/a~1b and never reads as a nested field. A field that was not set reads null. When something is created, every field it was created with is listed with before: null; when it is deleted, every field it had, with after: null.
No secret is ever recorded. A secret that changed shows as "***" on both sides, so you can see that it changed but not what it was or became. This covers:
- a credential’s value: a credential entry records only that one is set (
"value": "***"); - an API key’s material: the raw key, its hash and its prefix. A key is recorded by its
name,role,environmentandrevoked_at; - whatever the agent read masks:
llm.api_key(and each failover’s), the AWS secret key and session token,server_url.secret, each tool’swebhook_secret, and the values of MCP servers’headersandenv; - a phone number’s
server_url_secret(which the number read does return) and its own Twilio auth token.
Read it
403. A key reads only its own project’s entries. Entries come newest first. Filters, all optional:
next_cursor back as cursor until it is null.
Link to an entry
Every response to an audited request names one entry in theX-TurnCall-Audit-Id header: the one for the resource the request was about, such as the version a publish published.
request_id of every entry the request wrote, so one read returns all of them, the versions a publish archived and the numbers it re-pointed included, however many there are:
Append-only
- No route writes an entry. The only route is
GET /v1/audit-log. - The entry is written in the change’s transaction. A change that is rolled back leaves no entry, and a change that is saved always has one: the entries are written as the change commits, and a commit on these routes that has no entry is refused with a
500and its change rolled back, rather than saved unrecorded. - The database refuses changes too. A trigger on the
audit_logtable refuses everyUPDATEandTRUNCATE, and everyDELETEexcept the retention janitor’s. It protects the trail from the application and anything connecting as it; the table’s owner can still drop the trigger, so restrict who owns the database if that matters to you.
Retention
Entries are kept for good unless the operator setsAUDIT_LOG_RETENTION_DAYS on the server (1 to 36500; set but empty is the same as unset). With it set, each server deletes entries older than that many days once an hour, for every project. Servers sweeping at the same time skip each other’s rows rather than wait on them. It is a setting of the deployment, not of a project, so an admin key cannot shorten the trail that records its own changes.
Call retention never touches the audit log, and the audit log’s retention never touches calls.
A deleted project keeps its entries. Deleting a project writes a project deleted entry, and the project’s entries stay when the project is purged later (PROJECT_PURGE_RETENTION_DAYS), until the audit log’s own retention deletes them. Once the project is deleted its API keys stop working, so the entries can only be read from the database: SELECT * FROM audit_log WHERE project_id = '...' ORDER BY created_at DESC.