> ## 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.

# Coding sessions

> Coding-agent transcripts: a live tail, a bulk backfill, and the finalize that builds everything else.

## What this family does

Session ingestion is how Claude Code, Codex and similar clients feed Exo. One route accepts a single transcript line for a live tail, another accepts up to 500 events for a backfill, a third accepts client-detected agent failure signals, and a fourth reports progress.

The finalize route is the one that matters most. Session ingestion writes nodes and chunks, but it does not build graph edges, domains, the identity extraction or the first cognition cycle. Finalize does.

## Routes

| Method | Path                                                                                  | Scope   | What it does                |
| ------ | ------------------------------------------------------------------------------------- | ------- | --------------------------- |
| `POST` | [`/v1/ingest/agent-signals`](/api-reference/sessions/post-ingest-agent-signals)       | `write` | Ingest agent signals        |
| `POST` | [`/v1/ingest/session-batch`](/api-reference/sessions/post-ingest-session-batch)       | `write` | Ingest a session batch      |
| `POST` | [`/v1/ingest/session-event`](/api-reference/sessions/post-ingest-session-event)       | `write` | Ingest a session event      |
| `POST` | [`/v1/ingest/session-finalize`](/api-reference/sessions/post-ingest-session-finalize) | `write` | Finalize a session backfill |
| `GET`  | [`/v1/ingest/session-status`](/api-reference/sessions/get-ingest-session-status)      | `write` | Get session ingest status   |

## Headers that apply here

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

* **`X-Exo-Subject` is refused** on the routes here that scope by the key owner. Sending it returns 400 [`subject_not_supported`](/errors/subject_not_supported) rather than reading the wrong partition.

## The shape of a call

Send a batch, then finalize.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/ingest/session-batch \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -d '{"events": [{"sessionId": "sess_31a", "role": "user", "text": "Fix the flaky test"}]}'

  curl -X POST https://api.get-exo.com/v1/ingest/session-finalize \
    -H "X-Exo-API-Key: $EXO_KEY"
  ```

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

  headers = {"X-Exo-API-Key": EXO_KEY}
  httpx.post(
      "https://api.get-exo.com/v1/ingest/session-batch",
      headers=headers,
      json={"events": [{"sessionId": "sess_31a", "role": "user", "text": "Fix the flaky test"}]},
  )
  httpx.post("https://api.get-exo.com/v1/ingest/session-finalize", headers=headers)
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const headers = {
    "X-Exo-API-Key": process.env.EXO_KEY!,
    "Content-Type": "application/json",
  };

  await fetch("https://api.get-exo.com/v1/ingest/session-batch", {
    method: "POST",
    headers,
    body: JSON.stringify({ events: [{ sessionId: "sess_31a", role: "user", text: "Fix the flaky test" }] }),
  });

  await fetch("https://api.get-exo.com/v1/ingest/session-finalize", { method: "POST", headers });
  ```
</CodeGroup>

The response:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "sessionNodeCount": 128,
  "sessionEdgeCount": 341,
  "ingestPending": 0,
  "finalizePending": false,
  "building": true,
  "totalNodeCount": 128,
  "lastIngestedAt": "2026-07-14T09:12:03Z"
}
```

## Traps

**Events deduplicate, so there is no `Idempotency-Key` here.** Re-sending an event reports it as `deduped` instead of storing it twice. Per-event deduplication is stronger than per-request replay for a stream.

**Call finalize, once, after the last batch.** Skip it and the derived layers are never built. Call it twice and the second call is a no-op that returns `alreadyInFlight`.

**Poll `building`, not `warmLayers`.** The warm layers are data-dependent. An organization with no dated content never grows temporal phases, so waiting for every layer to turn true never finishes.

**The family refuses `X-Exo-Subject`.** Session data is scoped by the credential's own user. An explicit selector is 400 `subject_not_supported`.

## 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>
