> ## Documentation Index
> Fetch the complete documentation index at: https://docs.get-exo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ingestion

> Push content in and get a job id back. Every ingest route is asynchronous.

## What this family does

Ingestion accepts raw text, batches of items, and uploaded files: chat exports, documents and transcripts. Nothing is processed inline. Every route validates and durably queues the work, then returns a job id, and a background worker chunks, embeds and links the content into the knowledge graph.

Poll `GET /v1/ingest/{job_id}/status` for one job's progress, `GET /v1/jobs` for the cross-family view, or subscribe to the `job.completed` event and stop polling.

## Routes

| Method | Path                                                                    | Scope   | What it does                    |
| ------ | ----------------------------------------------------------------------- | ------- | ------------------------------- |
| `GET`  | [`/v1/import`](/api-reference/ingest/get-import)                        | `read`  | List imports                    |
| `POST` | [`/v1/import`](/api-reference/ingest/post-import)                       | `write` | Import a file                   |
| `POST` | [`/v1/import/transcript`](/api-reference/ingest/post-import-transcript) | `write` | Import a transcript             |
| `POST` | [`/v1/ingest`](/api-reference/ingest/post-ingest)                       | `write` | Ingest content                  |
| `POST` | [`/v1/ingest/batch`](/api-reference/ingest/ingest-batch)                | `write` | Ingest a batch of content items |
| `GET`  | [`/v1/ingest/{job_id}/status`](/api-reference/ingest/get-ingest-status) | `read`  | Get ingest job status           |

## Headers that apply here

Neither header is declared as an OpenAPI parameter, so neither appears in the generated schema tables.

* **`X-Exo-Subject`** selects which subject the call acts for on the subject-aware routes above. Omit it to act as the key owner. See [Subjects](/api-reference/subjects).
* **`Idempotency-Key`** makes a retry safe on `POST /v1/ingest`, `POST /v1/ingest/batch`. See [Idempotency](/platform/idempotency).

## The shape of a call

Ingest a piece of content.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/ingest \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 8f14e45fceea167a" \
    -d '{"content": "We dropped the freemium tier. Too much support load."}'
  ```

  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  import httpx

  response = httpx.post(
      "https://api.get-exo.com/v1/ingest",
      headers={"X-Exo-API-Key": EXO_KEY, "Idempotency-Key": "8f14e45fceea167a"},
      json={"content": "We dropped the freemium tier. Too much support load."},
  )
  job_id = response.json()["jobId"]
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const response = await fetch("https://api.get-exo.com/v1/ingest", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "Content-Type": "application/json",
      "Idempotency-Key": "8f14e45fceea167a",
    },
    body: JSON.stringify({ content: "We dropped the freemium tier. Too much support load." }),
  });
  const { jobId } = await response.json();
  ```
</CodeGroup>

The response:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "jobId": "b1f0c6d2-8a44-4b21-9a1e-6d1f0c6d28a441a8",
  "status": "pending"
}
```

## Traps

**202 means queued, not stored.** Content is not retrievable until the job reaches a terminal status: neither `pending` nor `running` on `GET /v1/ingest/{job_id}/status` (that route's own vocabulary calls the finished state `done`), or `succeeded` on `GET /v1/jobs/{job_id}`. A retrieve run straight after an accept returns the graph as it was before.

**Send `Idempotency-Key` on the JSON routes.** `POST /v1/ingest` and `POST /v1/ingest/batch` replay. The two import routes take multipart form data and do not, because the replay engine fingerprints a JSON body.

**`GET /v1/import` is history, not job status.** It lists the 20 most recent imports for the effective subject. `nodesCreated` and `domainsDetected` stay null until the worker finishes.

## Related

<Columns cols={2}>
  <Card title="API reference" icon="book-open" href="/api-reference/overview">
    Base URL, authentication, and the conventions every route shares.
  </Card>

  <Card title="Errors" icon="circle-alert" href="/platform/errors">
    The problem envelope and the full code catalogue.
  </Card>
</Columns>
