Retention is a clock: rows leave when their TTL passes. Log erasure is a command. It deletes what Rolter holds about one subject now, without shortening retention for everyone, and it records that it did so.
Use it for two requests: an erasure request (“delete everything you hold about this employee”), and the operational one, an engineer who pasted a secret into a prompt and needs that request’s captured body gone before its TTL.
Erasure is superadmin only (no deployment role reaches it), needs the control plane to have a ClickHouse URL, and is irreversible.
What gets deleted#
Rolter keeps two things per request in ClickHouse: the metadata row in request_logs (model, tokens, cost, status, the virtual key and its organization) and, when payload capture is on, the raw bodies in request_payloads. Nothing else is derived from them. There is no rollup or materialized view over either table, and the dashboard’s analytics are queries over request_logs, so deleting the rows removes the figures that were built from them too.
| Scope | Rows deleted |
|---|---|
virtual_key | every row sent with that key |
user | every row sent with a key that user created and that still exists |
org | every row of that organization |
request | the rows of one request id in one organization, narrowed to one instant when you give ts |
Request logs name a virtual key, never a person, so a user scope is resolved through the keys the user created. A key that has since been deleted leaves no record to resolve; erase by organization or by key id for those. A request id is whatever the caller sent and is not unique, so a request scope requires the organization, and ts (the row’s own timestamp, as the LLM Logs list shows it) when a client reuses one id.
Set include_logs to false to delete the captured bodies only and keep the metadata. That is the usual answer to a secret pasted into a prompt.
Payload rows written before ClickHouse migration 020_request_payloads_key.sql carry no key. Rolter finds the ones belonging to a key or user through their log rows and deletes them too, up to 10,000 per request; apply the migration so new rows carry the key themselves.
Erasure does not touch the audit log, MCP tool-call logs, or the response cache. A cached response expires with its own TTL.
It is not instant#
ClickHouse applies alter table … delete as a background mutation: the statement is queued and the affected parts are rewritten later. Rolter therefore never says “deleted” when it submits the request. It answers 202 with a record whose status is pending, and GET /api/v1/log-erasures/{id} reads system.mutations and moves the record to completed only once ClickHouse reports every mutation Rolter submitted as done.
| Status | Meaning |
|---|---|
pending | submitted; some mutation is not done yet. mutations_done of statements shows how far, and last_error shows why one is retrying. |
completed | every mutation is done. The rows are gone. |
failed | ClickHouse refused a statement at submission. Earlier statements may already be queued; submit again, which is safe to repeat. |
unverified | nothing was visible in system.mutations an hour after submission (the server was replaced, or pruned its history). Check by querying. |
Until a record says completed, treat the data as still present. A deployment with ClickHouse replicas or a distributed table finishes when the mutation has run everywhere; system.mutations on the server Rolter talks to is what is read.
In the dashboard#
Observability › Log erasure (superadmin) erases a virtual key, user or organization: choose the subject, paste its id, give the reason, and confirm. Delete the log rows too is on by default; turn it off to delete the captured bodies only. The recent erasures are listed below with their status, and a pending one keeps refreshing until ClickHouse reports it done. It reads Erased only for completed.
For a single request, LLM Logs has Erase captured payload in the detail of a row. It deletes that request’s captured bodies and keeps its metadata, asks for the reason first, and shows the status of the record under the button. A row that records no organization cannot be addressed this way; erase by virtual key instead.
The procedure#
The steps are the same in the dashboard and through the API; the examples below use the API.
- Find the subject’s id: the virtual key id from Virtual Keys, the user id from Members, the organization id, or the
request_idandtsof a row in LLM Logs. - Submit the erasure with a reason. The reason is stored and audited, so write the ticket or request reference, not the content.
- Poll the record until it reads
completed, and keep its id as the proof.
GET /api/v1/log-erasures lists the most recent hundred. Without a clickhouse_url the endpoints answer 503.
The record#
Every request writes a row to log_erasures in the control plane’s database and an audit entry (log_erasure.request, and log_erasure.complete when the first poll sees it finish). Both hold who asked, the scope, the ids that name it, the reason and the outcome. Neither holds a prompt, a completion or a key. They live in Postgres, not ClickHouse, so no erasure removes the record that it happened.
The audit entry for log_erasure.complete carries no actor: it is written by whichever poll first observes completion, and the request entry names who asked.