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

# Agentic streaming

> Reference for every frame in the POST /api/chat/v3 response stream.

> [!WARNING]
>
> **Preview endpoint, integration testing only.** `POST /api/chat/v3` is enabled per API key under a
> written testing agreement. Request text is sent to the model provider and recorded in Vera's
> tracing system, and the endpoint carries no availability or stability commitment.

> [!NOTE]
>
> **This is not the `/api/chat/v2` event format.** `/api/chat/v2` emits Vera's own flat event
> vocabulary (`thread-info`, `data-references`, `data-search-steps`). `/api/chat/v3` emits the **AI
> SDK UI message stream**, which has different frame types and a different terminator. Do not reuse
> a v2 parser. The frame shape is a preview contract and may change; the
> `x-vercel-ai-ui-message-stream` header carries its version.

## Transport

`Content-Type: text/event-stream`. Each frame is one JSON object:

```
data: {JSON_OBJECT}

```

The stream ends with a literal sentinel, which is **not** JSON:

```
data: [DONE]

```

Parse every `data:` line except `[DONE]`, and treat `[DONE]` as end-of-stream. Every frame carries a
top-level `type`.

## Response headers

| Header | Description |
| - | - |
| `x-thread-id` | The thread this answer is saved in. Use it to [reconnect](#reconnecting-a-dropped-stream). |
| `x-vera-request-id` | Correlation id for this request. Quote it in support requests. |
| `x-vercel-ai-ui-message-stream` | Version of the UI message stream protocol (currently `v1`). |

## Reconnecting a dropped stream

Every answer is saved in a thread under your partner account, so any of your API keys can reach
it. Vera keeps generating when your connection drops, so you can pick the answer back up:

```
GET /api/chat/v3/threads/{threadId}/stream
```

| Status | Meaning |
| - | - |
| `200` | The answer is still being generated. The stream is replayed from its first frame and then continues live, so discard what you had and parse it from the start. |
| `204` | The answer already finished. Nothing is replayed. |
| `404` | No such thread, or it belongs to another partner. |
| `500` | Branch on `code` in the body. `RESUME_UNAVAILABLE`: the answer could not be reached just now and may still be running, so retry this `GET` shortly rather than asking again. `ANSWER_NOT_SAVED` or `THREAD_NOT_FOUND_ON_RESUME`: the answer was lost, so ask the question again. |

Reconnecting replays an answer; it does not ask anything. To ask a follow-up in the same thread,
`POST` again with `thread_id` set to this id. Reconnecting after a follow-up resumes the latest
answer in that thread (see the
[tool guide](https://docs.verahealth.ai/docs/guides/agentic-tools.md#follow-up-questions)).

## Frame catalog

### Message and step envelope

`start`, `start-step`, `finish-step`, `finish`, `abort`

`start` opens the assistant message, `finish` closes it. The agent runs a bounded loop, so a single
answer contains several `start-step` / `finish-step` pairs: one per model call. `abort` appears
instead of `finish` when the run was cancelled.

```json theme={null}
{"type": "start", "messageId": "59442d8b-b368-4039-bf89-348a7467e6a7"}
{"type": "start-step"}
{"type": "finish-step"}
{"type": "finish"}
```

The `messageId` on `start` is the id Vera saves this answer under, inside the thread named by
`x-thread-id`. The non-streaming lane returns the same pair as `thread_id` and `message_id`. To
quote a request in a support request, use the `x-vera-request-id` header.

### `text-start` / `text-delta` / `text-end`

The answer. Concatenate every `delta` with the matching `id` to build the Markdown answer. The id
is `text-1` for the first block of a turn, `text-2` for the next, and so on.

```json theme={null}
{"type": "text-start", "id": "text-1"}
{"type": "text-delta", "id": "text-1", "delta": "Amoxicillin 1 g orally"}
{"type": "text-end", "id": "text-1"}
```

Text deltas carry Vera's citation markup verbatim, and so does the non-streaming answer: both modes
return the same text for the same question. See [Resolving a citation](#resolving-a-citation)
below for the tag grammar.

### `tool-input-available`

The agent called a tool you can render. `toolCallId` names the tool and its position in the turn,
as `<toolName>-<n>`: the first `medcalc` call of a turn is `medcalc-1`, the second is `medcalc-2`. The output
frame that may follow carries the same id, so you can attribute a payload without keeping any
mapping of your own.

For `patientHandout` and `priorAuth`, `input` **is** the deliverable: the model authors the
document as the tool's arguments and writes no answer text. It arrives trimmed to the fields
documented for that tool.

```json theme={null}
{
  "type": "tool-input-available",
  "toolCallId": "patientHandout-1",
  "toolName": "patientHandout",
  "input": {"title": "Your Care Guide for Cellulitis", "content": "..."}
}
```

For `medcalc`, the one card tool, `input` is empty: what the model asked for is its own working,
and the payload follows on `tool-output-available`.

```json theme={null}
{"type": "tool-input-available", "toolCallId": "medcalc-1", "toolName": "medcalc", "input": {}}
```

A `toolName` of `stop`, also with an empty `input`, means Vera declined the request on
content-policy grounds. No answer text follows and nothing about the reason is sent.

Neither `retrieve` nor `drug` ever appears: they do their work on the model channel, and what they
found reaches you as `data-sources` and answer text. A drug the answer relied on arrives as a
`drug` reference whose `title` is its name.

### `tool-output-available`

A card tool returned. `output` is the payload documented for that tool in the
[API reference](https://docs.verahealth.ai/api-reference/v3/ask-the-agent-with-a-chosen-tool-set.md), trimmed at every depth:
the monograph, the evidence and everything Vera's own clients do not render are removed before the
frame leaves the server. The frame carries `toolCallId` but not `toolName`; the id names the tool,
and matches the `tool-input-available` frame that announced it. A card tool announced there can end
without an output frame: when the lookup fails, or returns something the documented payload cannot
describe, nothing is sent for it and the answer text stands on its own.

```json theme={null}
{"type": "tool-output-available", "toolCallId": "medcalc-1", "output": {"calcId": "chad", "title": "CHA2DS2-VASc Score"}}
```

### `data-sources`

Sources the answer may cite. One frame is emitted per search the agent runs, carrying the sources
that search surfaced. A search that re-surfaces a paper an earlier one already sent will send it
again, identically, so **accumulate into a map keyed by `sourceId`** rather than appending to a
list. `sourceId` is what a `cite index` resolves against.

A frame is not tied to the step it arrives in. Sources are written when a background lookup that
attaches their evidence grade returns, which is usually a step later than the search that produced
them, so do not group sources by step or wait for a step to close before rendering.

A reference carries what a citation renders and links with, and no more. `sourceId` and `kind` are
always present. `kind` says what the source is: a `paper` (journal article or guideline) carries
`title`, `firstAuthor`, `journal`, `year`, its `doi` when it has one, which is the only identifier
to link out with, and `isGuideline`; a `drug` and a `calculator` each carry only their name as
`title`. `isGuideline` is a paper field and is absent on
every other kind, as is `evidenceStrength`, which is present when the paper has a graded level of
evidence (`Very High`, `High`, `Moderate`, `Low`). The evidence text, journal tiering and Vera's
internal scoring never leave the server, nor does the provider metadata the model attaches to a
part.

```json theme={null}
{
  "type": "data-sources",
  "id": "sources-1",
  "data": {
    "sources": [
      {
        "sourceId": 5,
        "kind": "paper",
        "title": "Community-Acquired Pneumonia Treatment Guidelines",
        "firstAuthor": "Metlay JP",
        "journal": "JAMA",
        "year": "2019",
        "doi": "10.1001/jama.2023.0001",
        "isGuideline": true,
        "evidenceStrength": "High"
      },
      {
        "sourceId": 7,
        "kind": "drug",
        "title": "Amoxicillin"
      }
    ]
  }
}
```

### `data-suggestions`

Follow-up questions, emitted once near the end of a completed answer.

```json theme={null}
{
  "type": "data-suggestions",
  "data": {"question_1": "...", "question_2": "...", "question_3": "..."}
}
```

Sent only when the request set `suggestions: true`, on both the streamed and the collected lane.

### `error`

Terminal stream failure. The text is a failure class plus the request id, deliberately generic.

```json theme={null}
{"type": "error", "errorText": "upstream_error - request a1b2c3d4"}
```

## Resolving a citation

A grounded claim is wrapped in a tag whose `index` names the evidence behind it:

```
<cite index="5-1">amoxicillin is first line for outpatient CAP</cite>
<cite index="5-2, 6-1">a macrolide is added for atypical cover</cite>
<cite index="7-1..7-3"/>
```

`index` is one or more `sourceId-spanNo` pairs: comma-separated for several sources, `..` for a
range, and the `spanNo` after the dash identifies the passage within that source. **Read the number
before the dash and look it up by `sourceId`** in the references you accumulated from
`data-sources`; the span number is Vera-internal and safe to discard. A self-closing tag (`/>`)
carries no claim of its own and cites the sentence it trails. `<calculator index="12"/>` marks
where a calculator card belongs and resolves by `sourceId` the same way.

Every id Vera cites was announced on a `data-sources` frame in the same response, or, on a
follow-up, in an earlier response in the same thread. Ids are numbered across the whole thread and
never reused, so keep the references from earlier answers and resolve against all of them. A lookup
that still misses means the model invented the index, so drop that tag rather than render an
unresolvable citation.

## Block tags around the answer

The answer can also carry a block tag that wraps a whole passage and marks what kind of statement it
is. These are not citations and carry no `index`: they open and close around prose you render
normally.

| Tag | What it wraps |
| - | - |
| `guideline` | A recommendation taken from a clinical guideline. |
| `guideline` with `authority` | A recommendation from a named national authority, by its slug. |
| `drug` | A statement drawn from a drug monograph. |

```
<guideline><cite index="1-1">Use amoxicillin 1 g orally three times daily.</cite></guideline>
```

Strip the tag and keep its contents. A tag you do not recognise should be stripped the same way —
the list can grow — and never rendered as literal text.

## Frames Vera does not send to partners

These exist in the underlying protocol and are filtered out before the stream reaches you. Do not
build against them; if one appears, report it.

| Frame | Why it is withheld |
| - | - |
| `reasoning-start` / `reasoning-delta` / `reasoning-end` | The model's private reasoning. It restates internal instructions and is not reviewed for clinical accuracy. |
| `tool-input-start` / `tool-input-delta` | Partial JSON of tool arguments. `tool-input-available` carries the complete input. |
| `tool-input-error` | The model repairing its own malformed tool call mid-loop. It is not a failure of the turn. |
| `tool-output-error` | A tool failed and the agent recovered on a later step. It is not a failure of the turn. |
| `tool-input-available` / `tool-output-available` for retrieval and lookup tools | The model's own working. Their result reaches you as `data-sources` and answer text. |
| `data-thread-id` | Vera's internal conversation id. The thread id is on the `x-thread-id` header instead. |
| `tool-approval-request` / `tool-output-denied` | Human-in-the-loop approval, which this endpoint does not run. |
| `source-url` / `source-document` / `file` | Not emitted by this engine. |
| `message-metadata` | Internal. |
| `providerMetadata` (on any frame) | The model provider's own bookkeeping — reasoning item ids, call ids. Stripped from every frame that reaches you. |
| `data-langfuse-trace-id` | Vera's internal trace id. Correlate with `x-vera-request-id` instead. |
| `data-ast-patch` | Internal rendering detail of Vera's own clients. |

## Parsing example

```js theme={null}
const response = await fetch("https://www.verahealth.ai/api/chat/v3", {
  method: "POST",
  headers: {"x-api-key": process.env.VERA_API_KEY, "Content-Type": "application/json"},
  body: JSON.stringify({question: "Usual adult dose of amoxicillin?", tools: ["drug"]}),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
const sources = new Map();
let answer = "";
let buffer = "";

for (;;) {
  const {done, value} = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, {stream: true});
  const lines = buffer.split("\n");
  buffer = lines.pop();

  for (const line of lines) {
    if (!line.startsWith("data:")) continue;
    const payload = line.slice(5).trim();
    if (payload === "[DONE]") continue;

    const frame = JSON.parse(payload);
    if (frame.type === "text-delta") answer += frame.delta;
    if (frame.type === "tool-input-available") console.log("tool ran:", frame.toolName);
    if (frame.type === "data-sources") {
      for (const source of frame.data.sources) sources.set(source.sourceId, source);
    }
  }
}
```

## Prefer a single JSON payload

Set `stream: false` and Vera returns one body once the answer is complete. `answer` carries the same
citation markup the stream does, resolvable against `references` exactly as above:

```json theme={null}
{
  "answer": "Amoxicillin 1 g orally three times daily is the usual adult dose <cite index=\"5-1\">for outpatient CAP</cite>.",
  "references": [
    {
      "sourceId": 5,
      "kind": "paper",
      "title": "Community-Acquired Pneumonia Treatment Guidelines",
      "firstAuthor": "Metlay JP",
      "journal": "JAMA",
      "year": "2019",
      "doi": "10.1001/jama.2023.0001",
      "isGuideline": true,
      "evidenceStrength": "High"
    },
    {"sourceId": 7, "kind": "drug", "title": "Amoxicillin"}
  ],
  "tool_outputs": []
}
```

The request id is on the `x-vera-request-id` header, not in the body.

An agentic turn can take a couple of minutes, so allow a generous client timeout on this path.

### Follow-up questions on this path

Send `"suggestions": true` and the body carries a `suggestions` object alongside `answer`:

```json theme={null}
{
  "answer": "Amoxicillin 1 g orally three times daily is the usual adult dose.",
  "suggestions": {
    "question_1": "When is high-dose amoxicillin indicated instead?",
    "question_2": "How does renal impairment change this dose?",
    "question_3": "What is the alternative for a penicillin allergy?"
  }
}
```

It is off by default. Generating them is a second model call made after the answer is already
finished, so on this path you wait for it and on the streaming path you do not.

> [card] [Agentic tools](https://docs.verahealth.ai/docs/guides/agentic-tools.md)
>
> Choosing which tools the agent may use.

> [card] [API reference](https://docs.verahealth.ai/api-reference/v3/ask-the-agent-with-a-chosen-tool-set.md)
>
> Full request and response schemas.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.