Before you start
- A key with the right scope. Erasure is deliberately narrow:
memory:forgetfor a single node,subjects:provisionfor a subject,adminfor a purge. See Scopes. - Certainty about the level. These verbs are not interchangeable, and three of the four cannot be undone.
- An export, if the data has any chance of being wanted again. Take it before, not after.
Pick the right verb
Export first
The export is the portability guarantee and, in practice, the only backup you get. Erasure has no undo at the subject level and above.1
Request the export
scope: "user" exports the effective subject. scope: "org" exports every member’s data and requires the admin scope, the same posture as org-wide usage reads.format is json. It is the only value the contract accepts.2
Poll until it is ready
bytes, counts and downloadUrl are null until the export succeeds. Read counts before you erase anything, because it is the last honest statement of how much existed.3
Download it
downloadUrl is an authenticated route, not a presigned blob link. It needs the same credentials as every other read, which means the document is never reachable by URL alone and a leaked link is not a leaked export. It also means you cannot hand the URL to a browser tab without the header.export_not_ready. Poll first.Forget one memory
DELETE /v1/graph/nodes/{id} is the erasure verb for a single node.
- The content is erased. The node’s chunks, which is where the retrievable and searchable text lives, are deleted outright. Search, retrieve, recall, brain training and the dashboard all stop seeing it. That is what makes this a real erasure verb rather than a hidden flag.
- The node row survives as a tombstone, for structure and audit, for 30 days. After the window it is physically purged.
node_not_found, never a 403, because a 403 would confirm the node exists.
Undelete restores structure, not content
contentRestored is always false. Forget erased the text and no undelete can bring it back. What returns is the node’s graph position, title, metadata and authority. Edges removed around the node stay removed. The restored node is visible and linkable but contributes no retrievable text until you write new content to it with PATCH /v1/graph/nodes/{id}.
Past 30 days you get the same 404 as a node that never existed, whether or not the collector has physically removed the row yet, because the tombstone is gone by contract at that point. Restoring a node that is not tombstoned is a no-op success with restored: false.
Erase one end user
DELETE /v1/subjects/{id} is the documented right-to-be-forgotten path for a single end user of your product.
subjects:provision, which an admin key also carries.
What it removes:
- Every row that subject owns in the tenant schema.
- The subject mapping and its internal user.
- The subject’s uploaded import files and finished export documents, deleted from storage after the rows commit. Erasure reaches blob storage, not only the database.
degradationReason: "brain_stale" until the organization retrains. See Training and serving an ExoBrain.
If the mapping’s internal user is not a service user, the call aborts with 409
subject_mapping_invalid before any row is erased. It fails closed. A corrupt mapping never results in a half-deleted subject.The two-phase purge
POST /v1/purge is the admin ceremony for erasing at scale. It runs in two phases, and the dry run is the default, so a single mistyped call cannot destroy anything.
1
Dry run: count the impact
counts so the payload stays readable, and totalRows is the sum of everything reported. Show these numbers to whoever asked for the deletion before you continue.2
Confirm within five minutes
counts keys, plus purgedAt.purge_impact_changed and nothing is deleted. Re-run the dry run, review the new numbers, confirm with the fresh token.
That is why purge is deliberately not replayable by Idempotency-Key. A replayed destructive call is a footgun, so every confirm must present a valid, unexpired, impact-bound token. See Idempotency.
What org scope does and does not touch
What org scope does and does not touch
scope: "org" empties the organization’s knowledge graph and keeps the organization itself, its users and its API keys. It also deletes uploaded imports, finished export documents and the trained ExoBrain, since the brain is derived from the graph being emptied. Retrieval falls back to the live rows until you retrain.subjectId is required for scope: "subject" and rejected for scope: "org".To delete the account itself rather than empty it, use DELETE /v1/me.Read `skipped` before you call it done
Read `skipped` before you call it done
skipped lists tables the purge could not clear: absent from an older schema, or blocked by a foreign key. It is present only when non-empty, precisely so a purge that left rows behind can never look identical to one that had none.If skipped is present, the erasure is incomplete. Do not report it as finished.Delete the account
X-Exo-Subject selector with it is a 400: deleting an account is not something you do on someone else’s behalf, so the selector is refused rather than ignored. Sandbox keys are refused too.
When it goes wrong
403 permission_denied on a delete
403 permission_denied on a delete
You are one scope short. Forgetting a node needs
memory:forget, erasing a subject needs subjects:provision, purging needs admin. A write key covers memory:forget but not subjects:provision. See permission_denied and Scopes.409 purge_impact_changed
409 purge_impact_changed
Data landed between your dry run and your confirm, so the token no longer matches reality and nothing was deleted. This is the guard working. Re-run the dry run, check the new counts, confirm with the new token. If it keeps happening, pause ingestion for that subject first.
409 export_not_ready
409 export_not_ready
You called the download route before the export finished. Poll
GET /v1/exports/{id} until status is succeeded, then follow downloadUrl. See export_not_ready.Retrieval degraded right after an erasure
Retrieval degraded right after an erasure
Expected. Erasing a subject marks the organization’s brain stale, so
degradationReason becomes brain_stale until you retrain. Results are still correct, they are served from a model that no longer matches the graph. See Brain path and hybrid path.I forgot a node and want the text back
I forgot a node and want the text back
There is no route that does this. Forget erases content by design, and undelete explicitly reports
contentRestored: false. If you have an export from before the deletion, re-ingest the content from it. If you do not, the text is gone.Next
Subjects and isolation
Why per-subject erasure is clean: the partition it is erasing is physical.
Belief and supersession
The verb to use when the belief changed rather than the person leaving.