The asymmetry is deliberate. Deleting request logs on a schedule is a privacy
obligation — they contain payload data. Deleting audit rows would destroy the
evidence of who changed a customer’s data, which is the one thing that has to
outlive a debugging window.
Request log
One row per request, holding:request_id— the same value returned in theX-Request-Idheader- tenant, API key, method, route
- status code and latency
- IP and user agent
- the request and response payloads, scrubbed and truncated
X-Request-Id is worth keeping. Quote it in a support ticket and
we can find your exact request.
Visible to company admins under API Usage → Request log, filterable by route
and status. Row-level security restricts it to your own company; the filtering is
enforced by the database, not by the page.
What is removed before storage
Nothing reaches the table raw. Two independent passes run first, because either alone leaves a hole: By field name — any field whose name containspassword, secret, token,
api_key, authorization, credential, cvv, card_number and similar. Names
are compared ignoring case and separators, so api_key, apiKey, api-key and
x-api-key are all caught by one rule.
By value shape — anything that looks like a credential wherever it appears,
including in a field with a perfectly innocent name:
- API keys (
f9_live_…,f9_test_…, legacywcrm_…) Bearer …tokens and JWTs (eyJ…)- Supabase keys
- card-number-shaped digit runs
notes field is exactly what happens in practice, and
the name pass alone would miss it.
Redacted values are replaced with [redacted]. Payloads over ~4 KB are truncated
and flagged with was_truncated.
Scrubbing runs before truncation, so a secret cannot survive by sitting past
the cut-off point.
Audit trail
One row per successful mutation —POST, PUT, PATCH, DELETE that
returned 2xx. It records who did what, and deliberately holds no payload:
request_id, tenant, API key, and the user the key acts for- method, route, action (
CREATE/UPDATE/DELETE), entity - status code, IP, timestamp
- Failed mutations are not audited. A rejected write changed nothing; recording it would claim a change that never happened. It is still in the request log for debugging.
- Reads are not audited. The trail answers “who changed this”, and
GETtraffic would drown that.
Immutability
UPDATE and DELETE are blocked by a database trigger. Not by convention or by
permissions — by the database itself:
Retention in practice
A scheduled job runs nightly at 03:40 and deletes request logs older than 30 days. It deletes in batches so a large backlog cannot hold a long transaction open against a table that is being written to constantly. Correctness does not depend on that job running: it only keeps the table small. The job never touchesapi_audit_log.
If you need something removed sooner
Request logs age out in 30 days on their own. For a specific erasure request, contact support with therequest_id — the audit trail will retain the fact that
a change occurred and who made it, which is a separate, legitimate record.