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

# Retrieving context

> Ask what someone knows and get back ranked sources, the conflicts in their own beliefs, the reasoning that produced the answer, and an honest flag when Exo is running on the fallback.

`POST /v1/retrieve` is the flagship call. One request returns evidence, contradictions and a reasoning trace, and tells you which engine served it.

## Before you start

* A key with the `read` scope. See [Authentication](/authentication).
* Content in the graph. A fresh organization returns an empty `sources` array. See [Ingesting content](/guides/ingesting).
* If you serve many end users, a provisioned subject to scope the call to. See [One memory per end user](/guides/subjects).

## The shortest call that works

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/retrieve \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "Where did we land on the pricing model?", "topK": 8}'
  ```

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

  res = requests.post(
      "https://api.get-exo.com/v1/retrieve",
      headers={"X-Exo-API-Key": os.environ["EXO_KEY"]},
      json={"query": "Where did we land on the pricing model?", "topK": 8},
  )
  body = res.json()
  print(body["path"], body["degraded"], len(body["sources"]))
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const res = await fetch("https://api.get-exo.com/v1/retrieve", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: "Where did we land on the pricing model?",
      topK: 8,
    }),
  });
  const body = await res.json();
  console.log(body.path, body.degraded, body.sources.length);
  ```
</CodeGroup>

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "query": "Where did we land on the pricing model?",
  "sources": [
    {
      "chunkId": null,
      "nodeId": "import:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:00042:9f2c4b6a1d8e",
      "title": "Pricing working session",
      "snippet": "Landed on seat plus usage. Usage-only punishes the teams we most want...",
      "score": 0.913,
      "cosineInSeed": true
    }
  ],
  "contradictions": [
    {
      "kind": "reversal",
      "subkind": "from_to",
      "node_id": "import:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:00117:3d5a8c1f7b04",
      "in_active_subgraph": true,
      "marker": "switched from usage-based only to seat plus usage",
      "before": "usage-based only",
      "after": "seat plus usage",
      "from_concept": "usage-based only",
      "to_concept": "seat plus usage",
      "tension": 0.65,
      "snippet": "We switched from usage-based only to seat plus usage after the June call."
    }
  ],
  "cognition": {
    "confidence": 0.83,
    "tier": "growing",
    "useSystem2": false,
    "driftRate": 0.02,
    "focalNodeIds": [
      "import:4a1e88f0-9c2d-4f31-b7a0-5e6c1d820b93:00042:9f2c4b6a1d8e"
    ]
  },
  "path": "brain",
  "degraded": false,
  "degradationReason": null,
  "latencyMs": 412,
  "traceId": "3f6c1d7a90b24e5aa1c8e2740b93df15",
  "usage": { "readUnits": 8, "embedTokens": 12, "brainInference": true }
}
```

<Note>
  `contradictions` on a retrieval is an open object in the contract, and the keys above are the ones this route emits today. They are the mining shape, in snake\_case, not the canonical `Contradiction` object. The stable, listable form with `id`, `a`, `b`, `status` and `detectedAt` comes from [`GET /v1/contradictions`](/api-reference/insights/list-contradictions).
</Note>

## Reading the response

### sources

Ranked evidence. Each entry names the graph node it came from, so you can drill in with `GET /v1/recall/graph/node/{nodeId}` or link a citation.

`snippet` is capped at 280 characters. To get the full stored text of every source, ask for it, which adds a `content` key alongside the snippet:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST "https://api.get-exo.com/v1/retrieve?expand=sources.content" \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "Where did we land on the pricing model?"}'
  ```

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

  res = requests.post(
      "https://api.get-exo.com/v1/retrieve",
      headers={"X-Exo-API-Key": os.environ["EXO_KEY"]},
      params={"expand": "sources.content"},
      json={"query": "Where did we land on the pricing model?"},
  )
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const res = await fetch(
    "https://api.get-exo.com/v1/retrieve?expand=sources.content",
    {
      method: "POST",
      headers: {
        "X-Exo-API-Key": process.env.EXO_KEY!,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: "Where did we land on the pricing model?" }),
    },
  );
  ```
</CodeGroup>

`expand` also works as a body field. `sources.content` is the only value defined in v1. The default is lean on purpose: full content multiplies the payload for a response most callers only rank and cite.

### contradictions

Where this person's own beliefs conflict. `a` and `b` are the two poles: for a reversal, the concept they moved from and to, and for a retraction, the belief before and after. `tension` is a 0 to 1 salience.

Every contradiction comes back with `status: "open"`, because v1 stores no resolution state. To close one, supersede the side that lost. See [Surfacing contradictions in review](/guides/contradictions-review).

Set `includeContradictions: false` to skip the detection pass if you do not use them.

### cognition

The brain's reasoning trace: `confidence` is how strongly it settled, `driftRate` is how far the activated region has moved from what the brain trained on, `focalNodeIds` are the nodes it settled on as the centre of the answer, and `useSystem2` is true when it escalated from a fast associative lookup to slow deliberate reasoning.

<Warning>
  `cognition` is **null** on the hybrid path. It is not an error, and it is not an empty object. The fallback engine has no reasoning state to report, so there is nothing honest to put there. Any code that reads `cognition.confidence` unconditionally will crash the first time an organization retrieves before its brain is trained.
</Warning>

### path, degraded and degradationReason

These three exist so a quality drop is never silent.

| `path`   | `degraded` | `degradationReason` | What happened                                                                           |
| -------- | ---------- | ------------------- | --------------------------------------------------------------------------------------- |
| `brain`  | `false`    | `null`              | The org's trained ExoBrain served the call.                                             |
| `brain`  | `true`     | `brain_stale`       | The brain served it, but the graph has changed since it trained. Retrain to clear this. |
| `hybrid` | `true`     | `no_brain`          | The org has never trained a brain. Vector plus keyword search served the call.          |
| `hybrid` | `true`     | `cold_start`        | There is not enough content to answer from yet.                                         |

Read [Brain path and hybrid path](/concepts/retrieval-paths) for the mechanism, and [Train and serve an ExoBrain](/guides/exobrain) to get off the fallback.

### usage

`readUnits` is the number of sources returned, which is the billable read unit. `embedTokens` is a documented estimate of the tokens spent embedding your query, at roughly 3.5 characters per token, not a metered figure. `brainInference` says whether the brain actually ran.

## Scoping the call

`scope` decides whose content is searched.

| Value            | Searches                                                                      |
| ---------------- | ----------------------------------------------------------------------------- |
| `user` (default) | The effective subject only: the key owner, or the subject in `X-Exo-Subject`. |
| `org`            | Every member and subject in the organization.                                 |

To retrieve as one of your end users, provision them once and then send the header on every call:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/retrieve \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "X-Exo-Subject: customer_6412" \
    -H "Content-Type: application/json" \
    -d '{"query": "What did this customer decide about pricing?"}'
  ```

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

  res = requests.post(
      "https://api.get-exo.com/v1/retrieve",
      headers={
          "X-Exo-API-Key": os.environ["EXO_KEY"],
          "X-Exo-Subject": "customer_6412",
      },
      json={"query": "What did this customer decide about pricing?"},
  )
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const res = await fetch("https://api.get-exo.com/v1/retrieve", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "X-Exo-Subject": "customer_6412",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query: "What did this customer decide about pricing?" }),
  });
  ```
</CodeGroup>

## Streaming

Set `stream: true` and the same result arrives as `text/event-stream`, so a UI can render evidence while the reasoning is still resolving. Events arrive in a fixed order:

```
event: sources
data: {"sources": [{"chunkId": null, "nodeId": "import:4a1e88f0-...:00042:9f2c4b6a1d8e", "title": "Pricing working session", "snippet": "Landed on seat plus usage...", "score": 0.913, "cosineInSeed": true}]}

event: contradictions
data: {"contradictions": [{"kind": "reversal", "subkind": "from_to", "node_id": "import:4a1e88f0-...:00117:3d5a8c1f7b04", "before": "usage-based only", "after": "seat plus usage", "tension": 0.65, "in_active_subgraph": true}]}

event: cognition
data: {"cognition": {"confidence": 0.83, "tier": "growing", "useSystem2": false, "driftRate": 0.02, "focalNodeIds": ["import:4a1e88f0-...:00042:9f2c4b6a1d8e"]}}

event: done
data: {"query": "...", "sources": [...], "contradictions": [...], "cognition": {...}, "path": "brain", "degraded": false, "degradationReason": null, "latencyMs": 412, "traceId": "3f6c1d7a90b24e5aa1c8e2740b93df15", "usage": {"readUnits": 8, "embedTokens": 12, "brainInference": true}}
```

Three things about that stream, because they are easy to get wrong:

* **Every frame is a wrapped object, not a bare value.** `cognition` arrives as `{"cognition": {...}}`, the same nesting as `sources` and `contradictions`. Read `JSON.parse(data).cognition`, not `JSON.parse(data)`.
* **`done` carries the whole envelope**, not a summary. It repeats `sources`, `contradictions` and `cognition` alongside `path`, `degraded`, `latencyMs`, `traceId` and `usage`. If you only want the final object, ignore the first three events and parse `done`.
* **There is no `[DONE]` sentinel.** The `done` event is the terminator. The result is computed before the first frame is sent, so the events are ordered rather than incremental.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -N -X POST https://api.get-exo.com/v1/retrieve \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "Where did we land on the pricing model?", "stream": true}'
  ```

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

  with requests.post(
      "https://api.get-exo.com/v1/retrieve",
      headers={"X-Exo-API-Key": os.environ["EXO_KEY"]},
      json={"query": "Where did we land on the pricing model?", "stream": True},
      stream=True,
  ) as res:
      for line in res.iter_lines(decode_unicode=True):
          if line:
              print(line)
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const res = await fetch("https://api.get-exo.com/v1/retrieve", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: "Where did we land on the pricing model?",
      stream: true,
    }),
  });

  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    process.stdout.write(decoder.decode(value));
  }
  ```
</CodeGroup>

`sources` always arrives first, so you can paint the evidence list before the model has finished settling. Wait for `done` before reading `path` and `degraded`.

## Choosing between retrieve and search

`POST /v1/search` is the same hybrid vector plus keyword retrieval with the cognition layer removed. No brain inference, no contradiction detection, no reasoning trace.

| Use                 | When                                                                                     |
| ------------------- | ---------------------------------------------------------------------------------------- |
| `POST /v1/retrieve` | You want the answer to be identity conditioned, or you need contradictions or cognition. |
| `POST /v1/search`   | You want cheap ranked chunks: autocomplete, a picker, a bulk pass over many queries.     |

`topK` on search is coerced into 1 to 50 rather than rejected. On retrieve, a `topK` outside 1 to 50 is a validation error.

## When it goes wrong

<AccordionGroup>
  <Accordion title="`cognition` is null and `degraded` is true" icon="circle-question">
    Not an error. The organization has no trained brain, so the hybrid path served the call and reported that honestly in `degradationReason`. The `sources` are real and usable. Train a brain to get the reasoning block. See [Train and serve an ExoBrain](/guides/exobrain).
  </Accordion>

  <Accordion title="403 permission_denied" icon="circle-question">
    Either the key lacks the `read` scope, or you sent an `X-Exo-Subject` value that has never been provisioned. The subject seam refuses an unknown selector before the handler runs, so this fails fast rather than silently reading the wrong partition. Provision it with `PUT /v1/subjects/{id}` first. See [`permission_denied`](/errors/permission_denied) and [`subject_not_found`](/errors/subject_not_found).
  </Accordion>

  <Accordion title="422 validation_error" icon="circle-question">
    `query` must be 1 to 10,000 characters and `topK` must be 1 to 50. The `errors[]` array in the problem body names the offending field and a JSON pointer to it. See [`validation_error`](/errors/validation_error).
  </Accordion>

  <Accordion title="503 service_degraded" icon="circle-question">
    A dependency, usually the embedder or the connection pool, is temporarily unavailable. Retrieve degrades to 503 rather than 500 by design, which means it is safe to retry with backoff. See [`service_degraded`](/errors/service_degraded).
  </Accordion>
</AccordionGroup>

## Next

<Columns cols={2}>
  <Card title="Cognition" icon="brain" href="/concepts/cognition">
    The reasoning block, field by field, and when it is null.
  </Card>

  <Card title="Conditioning your own agent" icon="sliders" href="/guides/conditioning">
    Turn the same graph into a prompt for a model you host.
  </Card>
</Columns>
