201 application/json
What it is
Organization, the account your API key belongs to (in practice, a physically separate database schema, not a filtered view of a shared table). User, a member of that organization (in practice, the human whose key it is, which is who a call acts as when no selector is sent). Subject, an end user of your product, whose memory lives inside your organization (in practice, an id you choose, likecustomer_6412, that becomes a separate memory partition).
The distinction that matters: your org is the tenant boundary Exo enforces at the storage layer. Subjects are the boundary inside it, enforced on every read and write by owner filtering. Both are real. Neither is optional.
Three levels, and who sees what
Subjects are not weaker than users. They are the same mechanism: both resolve to an owner that every query filters on. The difference is who provisions them and who they represent.
Selecting a subject
The selector is a header,X-Exo-Subject, not a body field. Send it on any call that reads or writes content, and that call acts as the subject instead of the key owner.
subjectDefault is not the selector
GET /v1/me reports a subjectDefault:
200 application/json
subjectDefault is always the key owner’s user id: the identity a request acts as when no selector is sent. It is deliberately not the resolved value of an X-Exo-Subject header you sent on that same call. If you are trying to confirm which subject a request acted as, the answer is the header you sent, not this field.
Provisioning and erasing
Subject ids are 1 to 128 characters of
A-Z a-z 0-9 . _ : -. Use your own stable identifier for the end user. Do not use anything you would mind seeing in a log.
DELETE /v1/subjects/{subject_id} is the documented right-to-be-forgotten path. It erases the subject’s rows, their mapping, and their internal user, then deletes their uploaded imports and export documents from storage once the rows commit. The organization’s ExoBrain is marked stale rather than deleted, because it was trained across every subject; retrieval keeps working on the fallback path until you retrain. See ExoBrain.
This is a different verb from forgetting one node. DELETE /v1/graph/nodes/{node_id} erases one memory with a 30-day structural undelete window. Subject deletion has no undelete. Read How a memory is made before choosing between them.
Scopes
Subject management is gated separately from ordinary reads and writes:
A
write key can ingest and retrieve as an already-provisioned subject but cannot create or erase one. That split lets your application servers carry a write key while only your control plane carries admin. See Scopes.
Which routes ignore the subject selector, and why
Which routes ignore the subject selector, and why
Three families do not take
X-Exo-Subject.POST /v1/brain/query and POST /v1/brain/answer read the API key owner’s own content by design.The subjects family itself takes the subject in the path, because there the subject is the object of the call rather than the identity performing it.POST /v1/purge takes subjectId in the body for the same reason, which is why the field is not named subject.DELETE /v1/me rejects the selector outright with a 400: deleting your whole account while pretending to be one of your end users is never what you meant.Does org scope let one subject read another?
Does org scope let one subject read another?
scope: "org" on POST /v1/retrieve and POST /v1/search widens the search to the whole organization. That is intended for internal team search over your own members’ content, not for a B2B2C deployment where subjects are your customers. If you provision one subject per end user, keep scope at its default of user, which filters to the effective subject.Authorization never follows the scope field. It stays on the API key’s principal.What a 403 on an unprovisioned subject looks like
What a 403 on an unprovisioned subject looks like
It is refused before the handler runs, so the failure is the same on every route that accepts the selector: 403
permission_denied. Provisioning is idempotent, so the fix is to call PUT /v1/subjects/{id} and retry. Calling PUT on every session start is the simplest way to never see this.Limits
- There is no cross-subject query. Nothing joins two subjects’ content.
- There is no route that returns the internal user id behind a subject.
- Subject erasure is not reversible and has no undelete window.
- The ExoBrain is per organization, not per subject.
- Data never crosses organizations. There is no cross-org read on any route, including the recall views.
Next
One memory per end user
The working guide: provision, select, and wire it into your product.
Provision a subject
Request shape, status codes, and the scope it needs.