Skip to main content
Everything Exo can do is here: 64 paths, 80 operations, 135 named schemas. This page covers what is true of all of them, so the family pages can get straight to what is true of theirs.
The API is in private preview. The routes and payloads on this site are the committed v1 contract. Keys are issued to design partners while the public rollout finishes, so there is no live playground here yet.

Base URL

Every path in this reference is absolute from that host and begins with /v1. There is no other version prefix, and no shipped route is ever renamed. See Versioning.

Authentication

Send an Exo API key on every request, as a header or as a bearer token. The two forms are equivalent.
Keys look like exo_<env>_<token>. They are minted at POST /v1/keys, shown exactly once, and revoked at DELETE /v1/keys/{key_id}. Only the SHA-256 hash is stored, and that hash is the id you list and revoke by. A missing or unrecognised credential is 401 authentication_error. A valid credential without the required scope is 403 permission_denied, and the problem body names the exact scope required. GET /v1/me is the right first call with a new key: it needs no data and no scope, so it answers whether the credential works before anything else can confuse the question.

Wire format

Request and response bodies are JSON with camelCase field names. nodeId, topK, degradationReason, subjectDefault. Some request bodies also accept snake_case for the same field, but camelCase is the documented form and the one every response uses. Path parameters appear as the contract declares them, which is snake_case: /v1/graph/nodes/{node_id}. That is a URL template, not a body field. Three conventions follow:
  • Timestamps are RFC 3339 in UTC, for example 2026-07-14T09:12:03Z.
  • Unknown fields are additive, not errors. New fields can appear inside an existing object at any time. Read tolerantly: ignore keys you do not know rather than failing on them.
  • Enumerated values are documented as strings, not closed enums, wherever a future internal value could appear. Connector status and job status are both like this.

Three headers the schema tables do not show

The generated request tables on each endpoint page come from the OpenAPI document, and the document does not declare these as parameters. They are also the conventions most likely to bite, so every endpoint page states by hand which of them apply to that route.

X-Exo-Subject

A subject is one end user of your product, with their own isolated memory partition. Provision one with PUT /v1/subjects/{subject_id}, then act as them by adding one header:
Subject selection is a header, not a body field. Some JSON-bodied routes also read a top-level subject field; the header is the documented selector because it works everywhere, including on GETs. Omit it and the call acts as the key owner, so single-user integrations stay one line. Routes fall into three groups, and every endpoint page says which one it is in: Two rules worth knowing. Provisioning is the privileged act, not selection: creating or erasing a subject needs subjects:provision, while acting as one needs no extra scope, because any read or write key in the organization has always been able to route to a provisioned partition. And a sandbox key cannot select out of its own partition: it gets 403 permission_denied if it tries. subjectDefault on GET /v1/me is the key owner’s internal user id, which is what a request acts as when no selector is sent. It is not a value you can send back as a selector: internal ids never leave the API in that role. See One memory per end user.

Idempotency-Key

Send any unique string. On a route that honours it, retrying with the same key and the same body replays the original response instead of acting a second time.
Twelve POST routes honour it. Every other route ignores it, and each endpoint page says which. Three rules catch people out:
  • A key reused with a different body is 422 idempotency_key_reuse. A key identifies one request, not one caller.
  • A show-once secret comes back redacted on a replay, never in the clear. A minted key’s plaintext and a webhook’s signing secret exist on the original response only.
  • A validation failure does not consume the key. The corrected request can reuse it.
Full detail, including the exact list of routes and the reason each excluded POST is excluded, is on Idempotency.

X-Request-Id

Every response carries one, including 204s and every error body, where it also appears as requestId. It is the value to quote when something looks wrong. See Request IDs.

Errors

Every failure is application/problem+json following RFC 9457.
code is the stable machine value to branch on, and it is always the tail of the type URI, which resolves to that code’s page in this documentation. detail is written for a human reading a log and names the specific id or field involved. suggestedAction, documentationUrl and errors[] are optional and appear where they help. The API returns 200, 201, 202, 204, 400, 401, 403, 404, 409, 422, 429 and 503. There is no 500 in the contract by design: not one of the 80 operations declares one. An unreachable dependency is 503 service_degraded, which is retryable, rather than an opaque server error. See Errors for the full catalogue.

Scopes

A route declares the finest scope it needs, and any broader scope that already reached it still does:
  • write grants graph:write and memory:forget
  • admin grants subjects:provision and usage:read
  • memory:forget grants graph:write
graph:write does not grant memory:forget. That asymmetry is the point: a key minted for an integration that edits the graph cannot destroy content. Every endpoint page names the scope it requires and every scope that also satisfies it. See Scopes.

Conventions every route shares

Asynchronous work is the other shared shape: ingestion, imports, exports and brain training all return a job id immediately. A 202 means the work is durably queued, not finished. Poll GET /v1/jobs/{job_id}, or subscribe to job.completed.

The families

Retrieval

The flagship. Ranked sources, contradictions, the reasoning behind the answer, and an honest degradation flag.

Search

Hybrid vector and keyword search with no reasoning layer. The cheap primitive.

Ingestion

Put text, batches, files and transcripts into the graph. Every route returns a job id.

Jobs

Watch and cancel the background work every ingest queues.

Subjects

Provision and erase one isolated memory per end user of your product.

Recall

Structured reads of identity, graph, insights and temporal phases.

Graph

Read and mutate nodes, edges and domains, with an audit trail behind every write.

Contradictions and proposals

Belief conflicts Exo found, and connections it proposes rather than assumes.

Condition

Turn an identity into a prompt any model you run can use.

Brain

Query, inspect and retrain the per-organization model over your graph.

Coding sessions

Stream coding-agent transcripts in, then finalize to build the layers on top.

Connectors

Connect an external source once and let Exo keep pulling. GitHub in v1.

Events and webhooks

A live SSE stream for a session, and signed deliveries for a system.

Identity

Introspect the credential you are holding.

Keys and sandbox

Mint, list and revoke credentials, including disposable sandbox keys.

Usage

Bucketed metering you can reconcile against your own billing.

Settings

Per-subject preferences, read, patched and reset.

Exports

Take everything out as JSON, through an authenticated download.

Team

The organization profile, and who is in it.

Account and purge

The two destructive operations, both behind a confirmation ceremony.

There is no SDK yet

Exo does not publish a client library. Rather than point you at a package that does not exist, here is what does. The OpenAPI document. The full contract is served at https://docs.get-exo.com/spec/exo-v1.json: 64 paths, 80 operations, 135 named schemas, and an x-exo-concepts index naming the seven objects that make this API different. Generate a typed client from it with whatever generator your stack already uses. Because no shipped route is ever renamed and /v1 evolves additively, a client generated today keeps working. See Clients and SDKs. The MCP server. If you are building inside a coding agent, connect Exo over MCP instead of writing HTTP calls yourself. See Integrations. These docs, as context. Every page here is available as Markdown by appending .md to its URL, and the whole site is exposed as an MCP server:
See Read these docs from your agent. When a client library does ship it will be generated from the same document, so nothing you build against the contract now becomes throwaway.