GDPR right-to-be-forgotten
A complete /v1/forget flow including audit-trail patterns and what doesn't get deleted.
A user emails support: "delete all my data". This recipe walks through running that request through Millimetric end-to-end, what's actually deleted, and the audit trail you should keep on your side.
TL;DR
curl -X POST https://api.millimetric.ai/v1/forget \
-H "Authorization: Bearer $SK_KEY" \
-H "Content-Type: application/json" \
-d '{ "user_id": "user_42" }'sk_* only. pk_* returns 403 forget_requires_secret_key — a leaked browser key cannot wipe data.
Step 1 — verify the request
You should always have your identity check between the email and the delete. The Worker doesn't authenticate the end user; it authenticates you via the sk_* key.
async function forgetUser(userId: string, requestedBy: string) {
// 1. Confirm the requester is who they say they are (your auth).
// 2. Log the request in your own audit table.
await db.insert("forget_requests", {
user_id: userId,
requested_by: requestedBy,
requested_at: new Date()
});
// 3. Then, and only then, call Millimetric.
}Step 2 — call /v1/forget
The mutation is queued on ClickHouse — ALTER TABLE events DELETE WHERE project_id = ? AND user_id = ?. Typical completion: seconds. For very large tables: minutes.
Step 3 — also delete from related systems
/v1/forget only handles Millimetric. Don't forget:
Your application database (the
usersrow, sessions, content).Stripe / billing provider (use their
Customer.delete).Email provider (Resend / Sendgrid suppression list).
Backups, if you're being thorough — usually documented in your privacy policy as "within X days".
What gets deleted in Millimetric
Every row in
eventswhereproject_id = <your project>ANDuser_id = <user>.
What does not get deleted
Anonymous events from before identify
They have user_id = NULL and are indistinguishable from any other anonymous traffic. By design — the system can't tell which anonymous events belong to a now-known user.
Aggregated rows in daily_rollup and sessions
Aggregates, not personal data. They never had PII. Re-aggregate from raw if you want to strip a user's contribution.
ip_hash rows
Already irreversible. The salt rotates daily, so within a day they're per-user, but cross-day linkability is gone.
Events under other projects
Scoped to the project owning the sk_* key.
If you also need to forget anonymous events
/v1/forget doesn't have anonymous_id support yet (PR welcome). Run the SQL directly against ClickHouse:
Get the anonymous_id from the user themselves (developer-tools snippet you ship them) or by stitching from user_id first:
Run forget by user_id first (kills post-identify events), then run the anonymous-id deletes for each one returned.
Audit trail — the part Millimetric doesn't do for you
Today the API doesn't yet write a structured audit row for /v1/forget calls. Until that's built, you keep your own audit log on the calling side:
Log:
Who made the request.
Who approved it.
Timestamp.
The exact response from
/v1/forget(so you have proof of the queued mutation).Other systems you also wiped (Stripe customer id, etc.).
Keep this audit log indefinitely — it's your compliance evidence.
Worked example
Errors
Status
error
Meaning
401
invalid_api_key
sk_* revoked or wrong project.
403
forget_requires_secret_key
You sent a pk_*.
400
invalid_payload
user_id missing or empty.
500
forget_failed
ClickHouse rejected the mutation. Worker logs have the trace. Retry.
See also
Privacy & retention — what's stored in the first place.
POST /v1/forget — endpoint reference.
Last updated
Was this helpful?