Skip to main content
Everything you put into Exo arrives through one of four routes, and all four are asynchronous. You get a job id back in milliseconds, a background worker chunks the content, embeds it and links it into the knowledge graph, and the finished job tells you how many nodes and edges it created.

Before you start

A key with write

Ingest routes need the write scope. Check yours with GET /v1/me.

The base URL

https://api.get-exo.com, with your key in X-Exo-API-Key.

A subject, if B2B2C

Writing on behalf of an end user means provisioning a subject first.

The shortest call that works

202 Accepted
That 202 means the content is durably queued, not that it is in the graph. Nothing is searchable until the job succeeds.

Which route to use

All four return the same accept envelope and all four are polled the same way.

The full loop

Send the content

Post to the route that matches your input. Add an Idempotency-Key header on POST /v1/ingest and POST /v1/ingest/batch so a network retry cannot create a second copy.
metadata is a free-form object. It is stored with the content and comes back on graph reads, so use it for your own foreign keys.

Poll the job

GET /v1/ingest/{jobId}/status reports the current phase and the phases already done.
Break on “no longer in progress”, not on one spelling of success. This route reports the worker’s own status vocabulary, where a finished job is done. Only GET /v1/jobs/{jobId} renames it to succeeded. A loop that waits for succeeded here never terminates.
200 OK
errorMessage is non-null only when status is failed.

Read what it created

GET /v1/jobs/{jobId} returns the finished job with a result block. This is the part worth logging: it tells you what your content became, not just that it uploaded.
200 OK
Any field the worker did not report stays null, and result itself is null until the job reaches a terminal status.

Batching

POST /v1/ingest/batch takes up to 500 items. Each item is an object with a required content of up to 100,000 characters, plus optional title, source_type and metadata. Every valid item becomes its own job, so one slow or failing item cannot hold up the rest.
202 Accepted
An item that cannot be queued, because its content is empty or oversized or its metadata is not an object, comes back in results as {"status": "skipped", "reason": "..."} and is counted in skipped. It never fails the batch. Read results in request order to find out which of your items did not make it.
failed is total minus succeeded, so skipped items are counted in both skipped and failed. Use skipped when you want to know how many items were rejected before enqueue.

Files and transcripts

POST /v1/import takes one file as multipart/form-data under the field name file.
For a meeting or interview, POST /v1/import/transcript adds one required form field, target_speaker. Only that speaker’s turns land in the subject’s graph, which is what you want when three people are in the room and only one of them is the subject.
GET /v1/import lists the 20 most recent imports for the current subject, newest first. nodesCreated and domainsDetected stay null until the worker finishes.
200 OK
See File and transcript imports for supported formats.

Retries that do not duplicate

Send an Idempotency-Key header on POST /v1/ingest and POST /v1/ingest/batch. A retry with the same key and the same body returns the original job id rather than queueing a second copy. That makes the accept safe to retry on a timeout, a 429 or a 503, which are exactly the moments you do not know whether the first attempt landed.
POST /v1/import and POST /v1/import/transcript are not documented as replayable. Uploading the same file twice creates two import jobs. Track your own upload state if double-imports matter to you.

Writing on behalf of a subject

By default every ingest is written to the API key owner’s partition. To write to an end user’s partition instead, provision the subject once and then send the X-Exo-Subject header on the ingest call.
The header is the selector. There is no body field that does this on the ingest routes. An unprovisioned selector is refused before the handler runs. See One memory per end user.

Canceling

POST /v1/jobs/{jobId}/cancel cancels a job that is still pending. A job the worker has already claimed cannot be canceled: cancellation never interrupts in-flight work.

When it goes wrong

Next

Retrieving context

Read the graph back, and understand path, degraded and the null cognition.

Reacting to change

Stop polling. Subscribe to job.completed over SSE or a webhook.