For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

/v1/forget only handles Millimetric. Don't forget:

  • Your application database (the users row, 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 events where project_id = <your project> AND user_id = <user>.

What does not get deleted

Thing
Why

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

Last updated

Was this helpful?