Skip to main content
Ingestion is asynchronous. You send content, you get a job, and you read what the job made. Returned by GET /v1/jobs/{job_id}.
200 application/json

What it is

Job, the unit of work an ingest becomes (in practice, the id you poll to find out what your content turned into, not merely whether the upload landed). Warm layer, a part of the cognitive state that the background cycle fills in after ingestion (in practice, domains, phases, the identity manifold and the insight queue: each one appears on its own schedule and is empty until it does). Tombstone, a node that has been forgotten but whose shell is kept for a 30-day window (in practice, the node’s text is erased immediately and irreversibly while its position in the graph stays restorable). Most memory APIs can add. Fewer can revise without losing the old version. Very few can forget in a way that survives a privacy audit, because forgetting properly means erasing the retrievable text rather than hiding a row. Exo separates those three verbs and gives each one its own semantics.

The two clocks

The fast clock finishes in one job. The slow clock runs on its own schedule over the whole graph, which is why the recall views can be empty for a while after a successful ingest. Nothing is wrong: the content is retrievable, the discovered structure is not built yet.

Stage by stage

1. Ingest

POST /v1/ingest takes text and returns 202 with a jobId. POST /v1/import takes a file the same way. Both are accept-then-work: nothing is embedded inside the request. Send an Idempotency-Key header and a retry returns the original jobId rather than a second copy of the content. See Idempotency.

2. Poll

GET /v1/ingest/{job_id}/status gives progress while it runs (status, phase, percent, completedSteps). GET /v1/jobs/{job_id} gives the finished interpretation: what the content became in the graph. Poll the first for a progress bar and read the second for the result. status moves through pending, running, then a terminal value. Treat it as a string rather than a closed set, so a future internal status does not break your reader.
The two routes spell success differently for the same job. GET /v1/jobs/{job_id} publishes pending | running | succeeded | failed | canceled. GET /v1/ingest/{job_id}/status reports the worker’s own vocabulary, where success is done. Poll for “no longer pending or running” and both routes agree.

3. Warm

The background cycle fills in the discovered layers. Watch them by reading the recall views rather than by waiting a fixed interval: GET /v1/recall/{recall_type} returns empty collections until a layer exists, and populated ones once it does. Insights, phases and the manifold each say what their own layer needs before it can appear.

4. Edit

PATCH /v1/graph/nodes/{node_id} changes a node in place. Setting content re-embeds it. metadata merges into the node’s frontmatter, and title replaces the title. Use this when the stored text was wrong. Use supersession instead when the text was right at the time and the person has since changed their mind.

5. Supersede

POST /v1/graph/nodes/{node_id}/supersede records that one belief replaced another, keeps both, and moves the authority. This is the default verb for a wrong belief. Belief and supersession covers it in full.

6. Forget

DELETE /v1/graph/nodes/{node_id} is the erasure verb, and it does two separate things.
200 application/json
It tombstones the node, which is auditable and reversible, and it erases the node’s chunks, which is neither. The chunks are where the retrievable and searchable text lives, so erasing them is what makes the content actually unrecoverable through POST /v1/retrieve and POST /v1/search rather than merely hidden behind a filter. Graph reads, recall, retrieve and brain training all filter the tombstone, so a forgotten node stops surfacing everywhere at once. Forgetting needs the memory:forget scope. A write key qualifies; graph:write alone does not, so a key minted for an integration that edits the graph cannot destroy content. See Scopes.

7. Undelete

POST /v1/graph/nodes/{node_id}/undelete restores a node inside the 30-day window.
200 application/json
contentRestored is always false. That is not a bug and not a degraded mode: forget deleted the text, and nothing can undo that. What comes back is the node’s graph position, title, metadata and authority. The restored node is visible and linkable but contributes no retrievable text until you write new content into it with PATCH.

8. Purge

POST /v1/purge is the two-phase administrative erase for a whole subject or a whole org. Phase one is a dry run that counts the exact impact and mints a confirmToken. Phase two spends the token and reports the rows it actually deleted, under the same keys. DELETE /v1/subjects/{subject_id} is the narrower path for one end user, and DELETE /v1/me tears down the account.

How to use it

Ingest, then poll until the job settles.
Rather than polling, you can subscribe: job.completed arrives on GET /v1/events and on webhooks. See Reacting to change.

Details worth knowing

Ingestion and the cognition cycle are separate clocks. Ingestion makes content retrievable. The cycle builds the discovered layers, and each layer has its own preconditions: phases need enough dated content to cluster, the manifold needs enough identity signals to decompose, insights need pairs of high-authority nodes that are related but not already linked. Empty is the honest answer for a layer that does not exist yet, and it is why the recall views return empty collections rather than an error.
Because a forget that could be undone is not an erasure. If the text were merely hidden it would still be sitting in the store, and a privacy request would not actually be satisfied. Exo erases the chunks at forget time and keeps only the shell, so contentRestored is false by construction. If you want a reversible hide, do not use forget: patch the node, or supersede it.
The tombstone is physically purged. Past the window, undelete returns exactly the same 404 as a node that never existed, whether or not the collector has removed the row yet, so the contract does not leak the difference between “gone” and “about to be gone”.
Both are idempotent. Re-deleting an already-tombstoned node is a no-op success. Undeleting a node that is not tombstoned returns restored: false and changes nothing, so a retried call after a network timeout never surprises you.
The job reaches status: "failed" with a human-readable error, and result stays null. A corrupt file or an embedding failure is terminal rather than retried forever. Cancel a job that has not started yet with POST /v1/jobs/{job_id}/cancel; a job already running returns job_not_cancelable.

Limits

  • Edges removed around a node when it was forgotten stay removed. Undelete restores the node, not its former neighbourhood.
  • PATCH with content collapses the node to a single chunk. Splitting long content into multiple chunks is the ingest pipeline’s job, so send large replacements as a new ingest.
  • Forgetting a node does not retrain the org’s ExoBrain. The brain marks itself stale and retrieval says so through degradationReason. See Brain path and hybrid path.
  • There is no bulk forget. Erasing everything for one end user is DELETE /v1/subjects/{subject_id}; everything else is node by node.

Next

Belief and supersession

The verb to reach for when the content was right and the person changed their mind.

Forgetting and portability

The task guide: tombstones, the GDPR path, the purge ceremony and export.