# Eve HTTP protocol reference

> Session routes, NDJSON event types, `continuationToken` follow-ups, stream reconnect rules, and client integration entry points.

- Repository: withastro/flue-with-vercel-eve
- GitHub: https://github.com/withastro/flue
- Human docs: https://grok-wiki.com/public/docs/withastro-flue-with-vercel-eve-f4b79875fff6
- Complete Markdown: https://grok-wiki.com/public/docs/withastro-flue-with-vercel-eve-f4b79875fff6/llms-full.txt

## Source Files

- `vercel-eve:docs/concepts/sessions-runs-and-streaming.md`
- `vercel-eve:docs/reference/typescript-api.md`
- `vercel-eve:docs/channels/overview.mdx`
- `vercel-eve:docs/guides/frontend/overview.mdx`
- `vercel-eve:packages/eve/src/public/index.ts`
- `vercel-eve:packages/eve/src/public/tools/index.ts`

---

---
title: "Eve HTTP protocol reference"
description: "Session routes, NDJSON event types, `continuationToken` follow-ups, stream reconnect rules, and client integration entry points."
---

Eve exposes a stable HTTP API under `/eve/v1` through the default Eve channel (`eveChannel()`). Session creation and follow-ups are JSON `POST` requests; progress is a durable NDJSON stream on `GET /eve/v1/session/:sessionId/stream`. The TypeScript client (`eve/client`), browser hooks (`useEveAgent`), and raw `curl` all target the same routes.

## Session handles

Two handles serve different jobs. Mixing them is the most common integration mistake.

| Handle | Role | Owned by |
| --- | --- | --- |
| `continuationToken` | Resume handle for sending the next user turn or HITL response | Channel |
| `sessionId` | Stream-and-inspect handle for attaching to the event feed | Runtime |

A session has one active continuation at a time. Each follow-up must use the current `continuationToken`; a stale token is rejected. For deterministic ordering, send one user turn at a time and wait for `session.waiting` before posting the next message to the same session.

<Note>
The client tracks a third cursor, `streamIndex` — the count of events already consumed. Persist the full `SessionState` object (`sessionId`, `continuationToken`, `streamIndex`) when resuming after a reload.
</Note>

## Route inventory

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| `GET` | `/eve/v1/health` | Public | Liveness probe |
| `GET` | `/eve/v1/info` | `localDev()` / `vercelOidc()` by default | Agent inspection snapshot |
| `POST` | `/eve/v1/session` | Channel `auth` policy | Create session and dispatch first turn |
| `POST` | `/eve/v1/session/:sessionId` | Channel `auth` policy | Continue session with follow-up or HITL response |
| `GET` | `/eve/v1/session/:sessionId/stream` | Channel `auth` policy | Stream durable NDJSON events |

Custom channels mount additional routes under their own paths. The Eve channel is enabled by default even without `agent/channels/eve.ts`.

:::endpoint POST /eve/v1/session
Create a durable session and dispatch the first user turn. Returns immediately; model work streams on the session stream route.
:::

<ParamField body="message" type="string | UserContent[]" required>
Plain text or an array of `text` and `file` parts. File parts require `type`, `mediaType`, and `data` (base64, data URL, or URL). Internal ref schemes (`eve-url:`, `eve-sandbox:`, `eve-attachment:`) are rejected.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
Ephemeral page context for this turn only. Strings become user-role context messages prefixed with `Client context:\n`. Objects are JSON-serialized. Never persisted to durable history.
</ParamField>

<ParamField body="outputSchema" type="object">
JSON Schema object requesting a structured turn result. Final payload arrives on `result.completed`.
</ParamField>

<ParamField body="mode" type="'conversation' | 'task'">
Optional run mode for the initial session.
</ParamField>

<ParamField body="callback" type="object">
Optional terminal session callback configuration.
</ParamField>

<ResponseField name="continuationToken" type="string">
Resume handle for follow-up turns on this session.
</ResponseField>

<ResponseField name="sessionId" type="string">
Durable session identifier for stream attachment.
</ResponseField>

<ResponseField name="ok" type="boolean">
Always `true` on success.
</ResponseField>

The response uses status **202 Accepted**. The `x-eve-session-id` header mirrors `sessionId`.

<RequestExample>

```bash title="Start a session"
curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Summarize the latest forecast."}'
```

</RequestExample>

<ResponseExample>

```json title="202 response"
{
  "continuationToken": "eve:7f3c4a2b-...",
  "ok": true,
  "sessionId": "ses_01h..."
}
```

</ResponseExample>

:::

:::endpoint POST /eve/v1/session/:sessionId
Continue an existing session. Requires the current `continuationToken` and at least one of `message` or `inputResponses`.
:::

<ParamField path="sessionId" type="string" required>
Durable session ID from the create response or `x-eve-session-id` header.
</ParamField>

<ParamField body="continuationToken" type="string" required>
Current resume handle. Missing or empty values return 400.
</ParamField>

<ParamField body="message" type="string | UserContent[]">
Follow-up user message. Same shape as the create route.
</ParamField>

<ParamField body="inputResponses" type="InputResponse[]">
HITL responses resolving pending `input.requested` events. Each entry requires `requestId`; include `optionId` for option picks or `text` for freeform answers.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
Same ephemeral context semantics as the create route.
</ParamField>

<ParamField body="outputSchema" type="object">
Per-turn structured output schema.
</ParamField>

<ResponseField name="sessionId" type="string">
Confirmed session ID.
</ResponseField>

<ResponseField name="ok" type="boolean">
Always `true` on success.
</ResponseField>

Status **200 OK** on success. The response does not rotate `continuationToken`; retain the token from create (or from client state) for subsequent follow-ups while the session is waiting.

<RequestExample>

```bash title="Send a follow-up"
curl -X POST http://127.0.0.1:3000/eve/v1/session/ses_01h... \
  -H 'content-type: application/json' \
  -d '{"continuationToken":"eve:7f3c4a2b-...","message":"Now send the short version."}'
```

</RequestExample>

:::

:::endpoint GET /eve/v1/session/:sessionId/stream
Attach to the durable NDJSON event stream for one session. Every event is recorded before a step completes, so the full stream is replayable.
:::

<ParamField path="sessionId" type="string" required>
Session to stream.
</ParamField>

<ParamField query="startIndex" type="integer">
Non-negative event offset. Omit to stream from the beginning; pass the number of events already consumed to reconnect without replaying them.
</ParamField>

<ResponseField name="Content-Type" type="string">
`application/x-ndjson; charset=utf-8`
</ResponseField>

<ResponseField name="x-eve-session-id" type="string">
Session ID echoed on every stream response.
</ResponseField>

<ResponseField name="x-eve-stream-format" type="string">
`ndjson`
</ResponseField>

<ResponseField name="x-eve-stream-version" type="string">
Current stream schema version (`15`).
</ResponseField>

Each line is one JSON event object. The stream sets `cache-control: no-store, no-transform` and `x-accel-buffering: no` to discourage proxy buffering.

<RequestExample>

```bash title="Stream from the start"
curl -N http://127.0.0.1:3000/eve/v1/session/ses_01h.../stream
```

</RequestExample>

<RequestExample>

```bash title="Reconnect after 42 events"
curl -N "http://127.0.0.1:3000/eve/v1/session/ses_01h.../stream?startIndex=42"
```

</RequestExample>

:::

## NDJSON event types

Events share a `type` discriminator. Most carry a `data` object with `sequence`, `turnId`, and step-scoped fields. Optional `meta.at` timestamps mark durable write time.

### Session and turn boundaries

| Event | Meaning |
| --- | --- |
| `session.started` | Durable session created; may include `runtime` identity and subagent `invocation` metadata |
| `turn.started` | New turn began (`turnId`, `sequence`) |
| `message.received` | Inbound user message accepted |
| `turn.completed` | Turn finished successfully |
| `turn.failed` | Turn failed; carries `{ code, message, details? }` |
| `session.waiting` | Session parked for next input (`data.wait: "next-user-message"`) |
| `session.completed` | Terminal session success |
| `session.failed` | Terminal session failure; carries `{ code, message, details? }` |

Turn boundary events for client consumption: `session.waiting`, `session.completed`, and `session.failed`. The TypeScript client stops a turn stream at the first boundary event.

### Model steps and assistant output

| Event | Meaning |
| --- | --- |
| `step.started` | Model step began |
| `step.completed` | Model step finished; carries `finishReason` and optional `usage` |
| `step.failed` | Model step failed; carries `{ code, message, details? }` |
| `reasoning.appended` | Reasoning delta (`reasoningDelta`, `reasoningSoFar`) |
| `reasoning.completed` | Finalized reasoning block |
| `message.appended` | Assistant text delta (`messageDelta`, `messageSoFar`) |
| `message.completed` | Finalized assistant text; `data.finishReason` distinguishes tool-call narration from terminal replies |
| `result.completed` | Structured output when `outputSchema` was requested (`data.result`) |

`message.completed` can fire more than once per turn when the model replies before requesting tools. Treat `finishReason: "tool-calls"` as non-terminal assistant text.

### Tools, HITL, and delegation

| Event | Meaning |
| --- | --- |
| `actions.requested` | Model requested tool calls (`data.actions`) |
| `action.result` | Tool result projected back (`status`, optional `error`) |
| `input.requested` | Run paused for approval or `ask_question`; carries `data.requests` |
| `subagent.called` | Subagent delegated; carries `childSessionId` for attaching to the child stream |
| `subagent.started` | Inline subagent execution began |
| `subagent.event` | Child stream event wrapped for inline subagents |
| `subagent.completed` | Delegated subagent finished |

### Compaction and connections

| Event | Meaning |
| --- | --- |
| `compaction.requested` | Context-window compaction began |
| `compaction.completed` | Compaction checkpoint written |
| `authorization.required` | Connection needs OAuth; `data.authorization` may include `url`, `userCode`, `expiresAt`, `instructions` |
| `authorization.completed` | Authorization resolved; `data.outcome` is `"authorized"`, `"declined"`, `"failed"`, or `"timed-out"` |

```mermaid
sequenceDiagram
    participant Client
    participant API as POST /eve/v1/session*
    participant Stream as GET .../stream
    participant Runtime as Durable workflow

    Client->>API: POST message (+ continuationToken on follow-up)
    API-->>Client: 202/200 JSON (sessionId, continuationToken on create)
    Client->>Stream: GET stream (?startIndex=N)
    Stream-->>Client: NDJSON turn.started
    Stream-->>Client: NDJSON message.appended / actions.requested / ...
    Stream-->>Client: NDJSON session.waiting
    Client->>API: POST follow-up with continuationToken
```

## Follow-up and HITL protocol

After `session.waiting`, post the next turn to `POST /eve/v1/session/:sessionId` with the stored `continuationToken`.

For human-in-the-loop pauses, the stream emits `input.requested` with `data.requests`. Each `InputRequest` includes `requestId`, `prompt`, optional `options`, and `display` (`"confirmation"`, `"select"`, or `"text"`). Respond with `inputResponses`:

```json title="Approve a tool call"
{
  "continuationToken": "eve:7f3c4a2b-...",
  "inputResponses": [
    { "requestId": "approval_1", "optionId": "approve" }
  ]
}
```

HITL-only deliveries retry up to 10 times when the server returns 500 with `target session was not found` (transient propagation window). Message-only deliveries do not retry automatically.

<Warning>
`clientContext` alone cannot dispatch a turn. It must accompany a `message` or `inputResponses` payload.
</Warning>

## Stream reconnect rules

Streams are durable and replayable. Clients reconnect after transient socket disconnects by resuming from the event count already consumed.

| Mechanism | Behavior |
| --- | --- |
| `?startIndex=N` | Server skips the first N recorded events |
| `streamIndex` in `SessionState` | Client-side cursor advanced after each consumed event |
| `maxReconnectAttempts` | Per-turn reconnect budget (default **3** on `Client` and `useEveAgent`) |
| Stream open retries | Up to **12** attempts at **250 ms** for statuses 404, 409, 425, 500, 502, 503, 504 |
| AbortSignal | Caller abort stops reconnect; treat as intentional stop |

On disconnect mid-turn, the client reopens the stream at `startIndex = eventsAlreadyConsumed` and continues until a turn boundary event. `session.stream()` exposes the same reconnect semantics for attaching to an existing session without sending a new message.

<Check>
Wait for `session.waiting` before sending the next user message. Eve does not guarantee FIFO ordering for concurrent deliveries to the same active session.
</Check>

## Error envelopes

Failed requests return JSON with `ok: false` and an `error` string. Common status codes:

| Status | Cause |
| --- | --- |
| 400 | Invalid JSON, missing required fields, bad message parts, invalid `startIndex` |
| 401 | Route auth walk exhausted (no `SessionAuthContext`) |
| 404 | Unknown `sessionId` on continue or stream |
| 413 | Attachment exceeds upload policy size limit |
| 415 | Attachment media type not allowed |
| 500 | `onMessage` handler threw (may include `errorId`) |
| 204 | `onMessage` returned `null` — message accepted but not dispatched |

Upload policy violations include a `violations` array with per-file details.

## Inspection and health routes

:::endpoint GET /eve/v1/health
Public liveness probe. Skips the channel auth walk entirely.
:::

<ResponseField name="ok" type="boolean">
`true` when the workflow is ready.
</ResponseField>

<ResponseField name="status" type="string">
`"ready"`
</ResponseField>

<ResponseField name="workflowId" type="string">
Active workflow identifier.
</ResponseField>

:::

:::endpoint GET /eve/v1/info
JSON inspection snapshot: model, instructions, tools, skills, channels, schedules, subagents, sandbox, connections, hooks, workflow, and workspace metadata. Uses the same default auth chain as the Eve channel (`localDev()`, `vercelOidc()`).
:::

## Client integration entry points

<CardGroup>
<Card title="TypeScript SDK" href="/eve-quickstart">
`eve/client` wraps POST + NDJSON with `Client`, `ClientSession`, and `MessageResponse`. Import stream helpers such as `isCurrentTurnBoundaryEvent` from the same entrypoint.
</Card>
<Card title="Browser hooks" href="/eve-sessions-streaming">
`useEveAgent` from `eve/react`, `eve/vue`, or `eve/svelte` projects events into `{ messages }`, handles reconnects, and persists `session` cursors.
</Card>
<Card title="Framework proxies" href="/eve-quickstart">
`withEve` (Next.js), `eve/nuxt`, and `eveSvelteKit` mount same-origin `/eve/v1/*` routes so browsers avoid cross-origin calls.
</Card>
</CardGroup>

### TypeScript client flow

```ts title="Script or server integration"
import { Client } from "eve/client";

const client = new Client({ host: "http://127.0.0.1:3000" });
const session = client.session();

const response = await session.send("Summarize the latest forecast.");
const result = await response.result();

console.log(result.status, result.message);

// Persist for resume
await save(session.state);
```

`MessageResponse` is single-use: consume it with `for await` or `result()`, not both. After abort, start a new `send()` for the next turn.

### Raw HTTP flow

<Steps>
<Step title="Create session">
`POST /eve/v1/session` with `{ "message": "..." }`. Store `sessionId`, `continuationToken`, and begin streaming.
</Step>
<Step title="Attach stream">
`GET /eve/v1/session/:sessionId/stream` with `curl -N` or a fetch reader. Parse one JSON object per line.
</Step>
<Step title="Wait for park">
Continue reading until `session.waiting`, `session.completed`, or `session.failed`.
</Step>
<Step title="Send follow-up">
If `session.waiting`, `POST /eve/v1/session/:sessionId` with `continuationToken` and the next `message` or `inputResponses`. Reattach to the stream (use `startIndex` to skip events already handled).
</Step>
</Steps>

## Authentication

Route auth is configured on `agent/channels/eve.ts` via `eveChannel({ auth: [...] })`. The ordered walk accepts the first `AuthFn` that returns a `SessionAuthContext`; skipped entries (`null`/`undefined`) fall through; exhaustion yields 401.

Default scaffold uses `[localDev(), vercelOidc()]`. Production browser traffic requires a custom verifier (Clerk, Auth.js, JWT). Pass bearer or basic credentials through `Client` `auth` and `headers`; function values re-resolve before every request including stream reconnects.

## Related pages

<CardGroup>
<Card title="Eve sessions and streaming" href="/eve-sessions-streaming">
Conceptual walkthrough of session cursors, HITL, subagent streams, and compaction events.
</Card>
<Card title="Runtime models" href="/runtime-models">
Compare Eve `continuationToken` and `sessionId` contracts with Flue sessions and workflow runs.
</Card>
<Card title="Eve quickstart" href="/eve-quickstart">
First `eve dev` session against the HTTP API.
</Card>
<Card title="Eve CLI reference" href="/eve-cli-reference">
`eve dev`, `eve info`, and local route discovery.
</Card>
</CardGroup>
