Handling a deletion request: the four calls and the right order

Revoke sessions, withdraw consent, unlink identities, then delete the user. Why order matters and which records you are supposed to keep.

A deletion request touches four Infrai endpoints, and running them in the wrong order leaves live sessions attached to a user record that no longer exists. The sequence that works: POST /v1/auth/session/revoke_all_for_user/{user_id} to close every device, POST /v1/auth/consent/revoke/{user_id} per category, DELETE /v1/auth/identity/remove/{user_id}/{identity_id} for each linked login method, then DELETE /v1/auth/user/delete/{user_id}.

Sessions first. Always.

Why that order

A revoked session is a closed session. A session attached to a deleted user is a question your code has to answer, and the answer differs between a cached JWT verify and a live lookup — which is precisely the ambiguity you don’t want in an audit.

Close the doors, withdraw the permissions, unlink the keys, then remove the record.

The sequence

curl -sS -X POST \
  "https://api.infrai.cc/v1/auth/session/revoke_all_for_user/au_usr_lMmXGySJeGVM1xicqHGJaJbB" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "au_usr_lMmXGySJeGVM1xicqHGJaJbB"}'
{ "ok": true, "data": { "ok": true, "count": 3 } }

Then the consents, one call per category you hold:

curl -sS -X POST "https://api.infrai.cc/v1/auth/consent/revoke/au_usr_lMmXGySJeGVM1xicqHGJaJbB" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "au_usr_lMmXGySJeGVM1xicqHGJaJbB", "category": "marketing"}'

Then the user itself:

curl -sS -X DELETE "https://api.infrai.cc/v1/auth/user/delete/au_usr_lMmXGySJeGVM1xicqHGJaJbB" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}"
{ "ok": true, "data": { "ok": true, "user_id": "au_usr_lMmXGySJeGVM1xicqHGJaJbB" } }

A second delete of the same id answers AUTH_USER_NOT_FOUND, which makes the whole routine safe to retry — useful, because a deletion job that can’t be retried is a deletion job that will one day be half-done.

As one idempotent routine

import os

import requests

API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
CATEGORIES = ("marketing", "analytics", "third_party", "essential")
SESSION = requests.Session()
SESSION.headers.update(HEADERS)


def erase_user(user_id: str) -> dict:
    """Close, withdraw, unlink, delete. Each step tolerates having already run,
    so the whole routine is safe to re-run after a partial failure."""
    report = {"user_id": user_id}

    revoked = SESSION.post(
        f"{API}/v1/auth/session/revoke_all_for_user/{user_id}",
        json={"user_id": user_id}, timeout=15,
    )
    report["sessions_closed"] = revoked.json().get("data", {}).get("count", 0)

    withdrawn = []
    for category in CATEGORIES:
        resp = SESSION.post(
            f"{API}/v1/auth/consent/revoke/{user_id}",
            json={"user_id": user_id, "category": category}, timeout=15,
        )
        if resp.json().get("ok"):
            withdrawn.append(category)
    report["consents_withdrawn"] = withdrawn

    listed = SESSION.get(f"{API}/v1/auth/identity/list/{user_id}", timeout=15)
    identities = listed.json().get("data", {}).get("items", []) if listed.ok else []
    for identity in identities:
        ident_id = identity.get("identity_id") or identity.get("id")
        if ident_id:
            SESSION.delete(f"{API}/v1/auth/identity/remove/{user_id}/{ident_id}", timeout=15)
    report["identities_removed"] = len(identities)

    deleted = SESSION.delete(f"{API}/v1/auth/user/delete/{user_id}", timeout=20)
    body = deleted.json()
    report["deleted"] = bool(body.get("ok")) or body.get("error", {}).get("code") == "AUTH_USER_NOT_FOUND"
    return report


if __name__ == "__main__":
    print(erase_user(os.environ["USER_ID"]))

Treating AUTH_USER_NOT_FOUND as success on the final step is the detail that makes it re-runnable.

What you delete, and what you keep

Not everything about a person is theirs to erase, and deleting too much is its own compliance failure.

DataActionWhy
Identity record, profile, metadatadeletethe request covers it
Live sessionsrevoke before deletingno orphaned access
Consent recordsrevoke, not deletethe withdrawal itself is the evidence
Invoices and payment recordskeepstatutory retention beats a deletion request
Aggregated analytics with no identifierkeepno longer personal data
Your own copies in other systemsdelete deliberatelythe platform can’t reach them

That last row is the one that gets missed. The user’s avatar in object storage, their queued jobs, their captured errors and their analytics events are separate resources and nothing here reaches them for you.

The upside is that they’re all on the same credential, so the fan-out is a handful of calls rather than a procurement exercise: DELETE /v1/storage/object/delete/{bucket}/{key} for stored files, POST /v1/queue/purge/{queue} where a queue is user-scoped, and GET /v1/errors/search to find what still carries their id. One key, one usage view, one place to prove you did it.

Limitations

There’s no single “erase everything about this subject” endpoint, and no export-then-delete bundle: a data-portability request is a set of reads you assemble yourself. Nor is there a scheduled grace period, so if your policy promises thirty days before permanent deletion, the timer is your own — put it on POST /v1/cron/create and have the job call this routine when it fires.

Auth0 ships more compliance tooling around this, including subject-access workflows, and a team whose main constraint is audit paperwork rather than engineering time would be better off there.

Every route in this routine reports billing_class: free in discovery — erasure isn’t billed per call. Identity is metered per monthly active user, and a deleted user stops counting in the month after they’re gone; read your own figure from GET /v1/account/usage (verified 2026-09-21) rather than trusting a printed number, and expect platform rates to keep drifting down.

References

Browse more auth developer guides