X-Exo-API-Key header on every request. There is no session, no login step and no token exchange in front of it.
Authorization:
cURL
What a key is
A key looks likeexo_<env>_<token>, where the middle segment names the deployment it belongs to. A production key is minted exo_prod_...; a test-mode key is minted exo_sandbox_.... The token is 43 URL-safe characters, so treat the whole string as opaque and never split it on anything but the first two underscores.
It is an organization credential bound to the user who minted it, and it resolves on every request to a principal: an org, a user, and a set of scopes. GET /v1/me shows you exactly what yours resolves to.
200 application/json
subjectDefault is a user id, not a subject key you provisioned.
subjectDefault is the identity your calls act as when you send no subject selector. It is always the key owner, and it is deliberately not the resolved value of an X-Exo-Subject header, because internal subject ids are an isolation detail that never leaves the API.
Selecting a subject
If you provision a subject per end user of your product, the selector is a header, not a body field:cURL
A handful of routes read the key owner’s own content by construction and refuse an
X-Exo-Subject selector outright: POST /v1/brain/query and POST /v1/brain/answer are the ones you are most likely to meet. Each endpoint page says so explicitly.Minting a key
Your first key comes from the Exo dashboard, because a signed-in dashboard session is allowed to mint. After that,POST /v1/keys mints from the API itself, and an API key must carry the admin scope to use it.
201 application/json
apiKeyis shown once. Only the SHA-256 hash is stored, so nothing can recover the plaintext afterwards. If you lose it, revoke the key and mint another.idis that hash, 64 hex characters. It cannot authenticate. It is the opaque idGET /v1/keyslists andDELETE /v1/keys/{id}takes, so it is safe to log and safe to show in your own dashboard.- Omitting
scopesinherits the caller’s. Naming a scope the caller does not hold is a403, so minting can never escalate.
Listing and revoking
GET /v1/keys returns the caller’s keys, newest first, active and revoked. Plaintext is never in this response.
200 application/json
lastUsedAt is the field to watch when a client “looks connected” but Exo has heard nothing from it. A key that stays null was never sent.
DELETE /v1/keys/{key_id} soft-revokes and returns 204 with no body. It is idempotent, so revoking an already-revoked key you own is another 204. A key that is not yours, in any sense, is a 404 rather than a 403, which also keeps key hashes unenumerable.
What a leaked key can and cannot do
This is the reason scopes exist, so it is worth stating in one place. A key that escapes into a log, a client bundle or a screenshot can do exactly what its scopes allow inside one organization, and nothing else.
The practical consequence: mint narrow keys per workload. A key that only ingests should hold
write and nothing more. A key that only serves reads should hold read. Read Scopes for the full vocabulary and what each one unlocks.
Revocation is the containment step. DELETE /v1/keys/{id} takes effect on the next request, and lastUsedAt on the remaining keys tells you what is still live.
Sandbox keys
A sandbox key is bound to an isolated partition of your org, so you can run the quickstart, a CI suite or a demo without putting anything into real memory. Mint one withPOST /v1/sandbox/keys, which needs admin.
201 application/json
- The prefix is
exo_sandbox_, so it is recognisable on sight. - Scopes are fixed at
readandwriteand are not caller-selectable. A test key must never be able to mint keys, provision subjects or erase real data. - It expires.
expiresAtsays when. Mint another to continue. - It is deliberately not idempotency-replayable. A retried
Idempotency-Keymints a fresh key rather than replaying the first response, because persisting a plaintext credential to save one key is the wrong trade.
POST /v1/sandbox/reset wipes the partition and keeps the keys, so your test credential keeps working against an empty graph. Full behaviour on Sandbox.
OAuth, and where it applies
Exo also speaks OAuth 2.1 with PKCE, but not on this API. It exists for MCP clients that cannot hold a static credential: the consumer Claude app on web, desktop and mobile, and the ChatGPT connector. Those clients point at Exo’s MCP endpoint, discover that it is OAuth-protected, send the user to a sign-in page, and receive a mintedexo_ key as the access token. One trust root, so revoking the key in the dashboard kills the connection.
Nothing about that flow changes how you call /v1/*. If you are writing server-side code, use a static key.
When it goes wrong
Every failure body is RFC 9457
problem+json with a stable code and a requestId. See Errors.
Next
Scopes
The full vocabulary, what each scope unlocks, and the two implications that surprise people.
Quickstart
Put the key to work in five calls.