---
title: "eve"
description: "The default HTTP API for an agent, covering session routes, auth, and customization."
---

The eve channel is the framework's default HTTP API. It's what the terminal UI, [`useEveAgent`](../guides/frontend/overview), `curl`, and any SDK client talk to when they start sessions, send messages, and stream events. The selected `channels/eve.ts` source owns the complete `/eve/v1` surface, including health, inspection, callbacks, task input, and session routes. eve supplies that source when `agent/channels/eve.ts` does not exist.

Every running eve app exposes its own API. `eve.dev` publishes framework documentation; it is not a shared API, authorization server, MCP server, or A2A server. Each deployment supplies its own host and authentication policy.

Reach for it when something needs HTTP access to your agent, including local tooling, a browser frontend, the terminal UI, or another API client. Most apps never write this file. Add `agent/channels/eve.ts` only to override the defaults, usually the route auth policy.

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [vercelOidc(), localDev()],
});
```

## Web Chat with Sign in with Vercel

Run `eve add channel/web`, or select **Web Chat** from the terminal UI's channel
picker. Choose **Next.js**, a hosting layout, then **Sign in with Vercel**. Sign
in with Vercel is available only for the Next.js Web Chat app. Setup uses a
linked Vercel project and creates a Vercel App that only members of that
project's team can sign in to.

For a new agent, initialize it first, then link a project and add Web Chat:

```bash
eve init my-agent --non-interactive
cd my-agent
eve link
eve add channel/web
```

`eve init --channel-web-nextjs` keeps its existing scaffolding behavior and does
not provision authentication. Sign-in setup runs through `eve add channel/web`
or the terminal UI's channel picker.

The generated Next.js app uses Better Auth sessions. The selected agent's HTTP
channel verifies the same session, while retaining Vercel OIDC and local
development access. In an agent workspace, setup configures only the selected
agent. Other agents keep their own authentication policies.

Setup registers `/api/auth/callback/vercel` as a project redirect URI, so Vercel
validates callbacks against the project's deployment domains. The app uses the
`authorization_code` grant, `client_secret_post` authentication, and the
`openid`, `email`, and `profile` scopes. Setup writes `VERCEL_APP_CLIENT_ID`,
`VERCEL_APP_CLIENT_SECRET`, and `BETTER_AUTH_SECRET` to production and preview;
secret values are marked sensitive and are never printed or written to source.
Run `eve deploy` after setup to publish the app. Local development continues
without browser sign-in.

For automation, supply the setup answers explicitly:

```bash
eve add channel/web --non-interactive \
  --answer 'web-hosting="vercel"' \
  --answer 'web-authentication="vercel"'
```

Use a linked project with permission to manage Vercel Apps and project
environment variables. A team owner can complete setup if your role cannot
create apps. The searchable team picker lists eligible teams first. Disabled
teams show `needs permission` or `couldn't verify`, with shared recovery guidance
below the list. If permissions cannot be verified, that team stays
disabled until you sign in or retry. Linked projects are checked before
interactive auth setup.
If setup reports a missing prerequisite, complete it and resume with
`eve add channel/web --skip-install`. Compatible existing apps
and credentials are reused; conflicting app settings or credentials stop setup
with an error. Authored channel or UI files are preserved unless you explicitly
pass `--overwrite`.

If the team has reached its Vercel App limit, remove an unused app in team
settings and retry. If a generated app name or slug is taken by an incompatible
app, rename the Vercel project in its settings and retry. Compatible apps from a
concurrent setup are reused. A client-secret limit requires removing an unused
secret from the app's settings; setup never removes an existing secret for you.

Choose **Configure authentication myself** (`web-authentication=custom`) to
keep the current channel policy. The default placeholder rejects production
browser requests until you configure your own authentication.

## Routes

The default eve channel inspects the agent, creates sessions, accepts callbacks and task input, controls sessions, and streams events. Its public routes include:

- `GET /eve/v1/health` (check whether the application is reachable)
- `GET /eve/v1/info` (inspect the agent)
- `POST /eve/v1/session` (create a session, optionally sending its first message)
- `POST /eve/v1/session/:sessionId` (send a follow-up)
- `POST /eve/v1/session/:sessionId/cancel` (cancel the in-flight turn)
- `POST /eve/v1/session/:sessionId/clear` (clear the session's model history)
- `POST /eve/v1/session/:sessionId/compact` (compact the session's context)
- `POST /eve/v1/session/:sessionId/reset` (retire the session)
- `GET /eve/v1/session/:sessionId/stream` (stream events as NDJSON)

The session routes use only durable session IDs. Create a session explicitly, then put its returned ID in every follow-up, control, and stream path.

`GET /eve/v1/health` is public and returns `{ ok: true, status: "ready", workflowId: string }`. `GET /eve/v1/info` uses the channel's auth policy and returns agent-info version 6. The TypeScript client validates both successful payloads: malformed health JSON throws `HealthResponseError`, malformed inspection JSON throws `AgentInfoResponseError`, and a non-success response from either route throws `ClientError`.

### Start and continue a session

Create a conversation session before its first message with a bodyless request:

```bash
curl -X POST https://<deployment>/eve/v1/session
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```

See [Start a session](../concepts/sessions-runs-and-streaming#start-a-session) for the
initialization boundary, first-turn identity, readiness response, and retry behavior.

The create request still requires route auth. Session audience and the timeout are selected
at creation; they do not change when the first message arrives. Turn-scoped fields such as
`clientContext` and `outputSchema` require a message on the create request.

Include an initial message to create the session and start its first turn in one request:

```bash
curl -X POST https://<deployment>/eve/v1/session \
  -H "Content-Type: application/json" \
  -d '{"message":"What is the weather in Paris?"}'
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```

Authenticated callers that may retry a create request can pass their own `operationId` for
create-once semantics. Once the operation owner is active, the same operation under the same
authenticated principal returns that session instead of dispatching the input again. The create
route does not wait for a concurrently starting request to publish ownership: simultaneous
requests can receive different accepted candidate IDs, while only the candidate that claims the
operation runs its first turn. Retry the operation after startup when you need its canonical
session ID. Anonymous callers cannot use `operationId`, and operation ownership expires when the
session is no longer resumable.

```bash
curl -X POST https://<deployment>/eve/v1/session \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"message":"What is the weather in Paris?","operationId":"order-4213-research"}'
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```

When the create request includes a message, a follow-up request accepts exactly one of
`message` or `inputResponses`; use the latter to answer a pending HITL request:

```bash
curl -X POST https://<deployment>/eve/v1/session/wrun_A \
  -H "Content-Type: application/json" \
  -d '{"inputResponses":[{"requestId":"req_A","optionId":"approve"}]}'
```

Follow-up messages use `turnPolicy: "steer"` by default. Before assistant answer output or local tool execution begins, steering interrupts pending model generation and applies the correction in the same turn. Executing tools finish and checkpoint their results first. After answer output starts, steering waits for the next committed workflow boundary. Reasoning and search progress do not count as answer output; interrupted provider-managed search may run again. The turn ID stays the same.

Set `turnPolicy: "queue"` on `eveChannel(...)` when follow-ups should wait for active turns to finish. `inputResponses` answer their addressed requests.

Sending a message to an unknown or terminal session ID returns `409` with
`{"code":"session_not_active","error":"The session is no longer active.","ok":false}`.
An existing workflow whose inbox is not yet available returns `409 session_not_ready` instead.
A session that another eve version built returns `409 session_stranded` with the `eveVersion`
that built it.

### Send files

A message can mix text and file parts. A file part's `data` is base64, a `data:` URL, or a URL:

```bash
curl -X POST https://<deployment>/eve/v1/session/wrun_A \
  -H "Content-Type: application/json" \
  -d '{"message":[{"type":"text","text":"Summarize this report."},{"type":"file","mediaType":"application/pdf","filename":"report.pdf","data":"data:application/pdf;base64,JVBERi0xLjQK..."}]}'
```

`uploadPolicy` sets the size cap and allowed media types, 25 MB and every type by default. A request whose inline file breaks the policy returns `413` for size or `415` for type. Inline bytes also count toward the request body limit of the platform that serves the channel. On Vercel Functions that limit is 4.5 MB, so a base64 file stops at about 3.3 MB.

For larger files, upload them from the browser to your own storage and send the URL in the file part. Add `fetchFile` to load the URLs you own inside the workflow step, so the bytes never pass through the request or the workflow start payload:

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [vercelOidc(), localDev()],
  uploadPolicy: { maxBytes: 100 * 1024 * 1024 },
  async fetchFile(url) {
    if (!url.startsWith("https://uploads.example.com/")) return null;
    const response = await fetch(url, {
      headers: { authorization: `Bearer ${process.env.UPLOADS_TOKEN}` },
    });
    if (!response.ok) throw new Error(`Upload fetch returned HTTP ${response.status}`);
    return Buffer.from(await response.arrayBuffer());
  },
});
```

`fetchFile` follows the [custom channel contract](./custom#file-uploads): return bytes to stage the file, or `null` for a URL that isn't yours. eve then downloads a public `https:` URL itself, and any other URL reaches the model as a note. `uploadPolicy` covers every file eve stages for the channel, including URLs that `fetchFile` loads and URLs eve downloads itself. eve checks the final bytes and the media type they prove, so a file over the cap, or one whose bytes are a disallowed type, reaches the model as a note instead of failing the request. See [Inbound attachments](../sandbox#inbound-attachments) for how eve stores files and shows them to the model.

### Stream events

Stream a session as newline-delimited JSON from `GET /eve/v1/session/:sessionId/stream`. The [session protocol](../concepts/sessions-runs-and-streaming#stream-a-session) defines the event set, envelopes, cursors, and reconnection behavior.

Following a stranded session's stream returns `409 session_stranded`, just like a send. The TypeScript client reports `ClientSessionStrandedError` without retrying. Pass `follow=false` to read the recorded history instead: the response ends at the durable tail, carries the `x-eve-stream-tail-index` header, and never inspects or changes the session, so it works before a reset. A reset attempts to append `session.failed` with code `session_stranded` and close the stream. Existing readers finish when they receive that terminal event.

### Cancel a turn

Post to `/eve/v1/session/:sessionId/cancel` to request cancellation of the active turn. You can include the observed `turnId` to keep a late request from cancelling a newer turn. Cancellation is asynchronous; confirm the turn boundary on the stream as `turn.cancelled` followed by `session.waiting`.

See [Cancel the in-flight turn](../concepts/sessions-runs-and-streaming#cancel-the-in-flight-turn) for response statuses, HTTP status codes, subagent cancellation, and race behavior.

### Clear context

Post to `/eve/v1/session/:sessionId/clear` to remove model-message history while preserving the session ID, system prompt, tools, skills, durable state, limits, and sandbox. A stranded session cannot run the clear, so the route returns `409 session_stranded` and changes nothing; post to the `reset` route to end it instead. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).

### Compact context

Post to `/eve/v1/session/:sessionId/compact` to summarize context without sending a user message. The operation waits for an active turn to settle and reports its result on the stream. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).

### Reset a session

Post to `/eve/v1/session/:sessionId/reset` to terminally retire that session. Reset never replaces the ID automatically; create a new session explicitly for a fresh conversation. Reset also works on a stranded session: eve cancels the stranded workflow run directly instead of delivering the command to it. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).

## Replace or disable the defaults

An authored `agent/channels/eve.ts` replaces the complete default eve channel. An `eveChannel(...)` replacement keeps the standard route set with your options; a custom `defineChannel(...)` replacement exposes only the routes you declare. Health, inspection, and callbacks do not reappear through a hidden host fallback.

Disable the complete surface by exporting `disableRoute()` at that slot:

```ts title="agent/channels/eve.ts"
import { disableRoute } from "eve/channels";

export default disableRoute();
```

The default home page is a separate `channels/home.ts` source that serves `GET /` and `HEAD /`. Author `agent/channels/home.ts` to replace it or export `disableRoute()` there to remove it without affecting `/eve/v1`.

## CORS

The eve channel leaves CORS untouched by default. Pass `cors: true` to enable
permissive browser CORS with preflight handling, or pass an options object to
narrow origins, methods, and headers. Route auth still runs on the actual
session requests.

Enable or narrow CORS only when browser clients call the channel directly:

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [vercelOidc(), localDev()],
  cors: {
    origin: "https://app.example.com",
    methods: ["GET", "POST"],
    allowedHeaders: ["authorization", "content-type"],
  },
});
```

## Authentication

The `auth` option decides who can call `/eve/v1/info` and the session routes. The built-in helpers cover development and trusted infrastructure:

- `localDev()` accepts requests during local development.
- `vercelOidc()` lets the local CLI reach a deployed agent, and lets other internal deployments from your team call it.

Neither admits browser users or external clients in production. For a public app, wire the channel to your own auth (Clerk, Auth.js, your own OIDC/JWT verification, an API-key verifier, or any custom `AuthFn`). Vercel OIDC is optional; use it only when Vercel-issued deployment tokens are part of your trust model.

`eve init` scaffolds an `agent/channels/eve.ts` with a production placeholder so you replace it before going live. The generated channel checks Vercel OIDC before falling back to localhost access, and includes `placeholderAuth()`, which returns a setup-focused 401 in production until you swap it for real auth. Delete the file and eve selects its default channel source with `[vercelOidc(), localDev(), placeholderAuth()]`, which rejects all production traffic.

For the full auth model and helper list, see [Auth & route protection](../guides/auth-and-route-protection).

## Audience

The eve channel assigns an observability audience when it creates a session. The
audience controls whether instrumentation may capture session content:

| Session creator                           | Default audience |
| ----------------------------------------- | ---------------- |
| Anonymous caller                          | `unknown`        |
| `user`, `service`, or `runtime` principal | `private`        |
| Any other principal type                  | `unknown`        |

Anonymous HTTP surfaces are `unknown` by default because reachability does not establish that their content is safe to record. Authenticated `user`, `service`, and `runtime` sessions are private because they belong to one identified party. Other authenticated principal types also remain `unknown`: trace consumers record metadata but omit content by default in preview and production. Set an explicit `audience: "public"` only for intentionally public traffic.

For a production endpoint whose conversations are all intentionally public,
set a constant audience:

```ts title="agent/channels/eve.ts"
import { vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";

export default eveChannel({
  auth: [vercelOidc()],
  audience: "public",
});
```

`audience` controls observability content capture; it does not change who may
call the endpoint. `auth` still controls access. For a classification that
depends on channel state or application-specific verified data, use a [custom
channel audience](./custom#conversation-audience).

You can also pass a function that receives `caller`, channel, and deployment
environment. Classification is fixed at session creation, so a later
turn from a different caller does not reclassify the session.

For a remote agent, a trusted dispatch can forward the original audience and
its trace-content ceiling. See [Preserving trace
content](../guides/remote-agents#preserving-trace-content).

## Customization

Use `onMessage` to add request-specific context before the agent sees the user message, and `events` to observe stream events from sessions this channel created:

```ts title="agent/channels/eve.ts"
import { eveChannel, defaultEveAuth } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [vercelOidc(), localDev()],
  onMessage(ctx, message) {
    const callerId = ctx.eve.caller?.principalId ?? "anonymous";
    return {
      auth: defaultEveAuth(ctx),
      context: [`HTTP caller ${callerId} sent: ${message}`],
    };
  },
  events: {
    "message.completed"(eventData, _channel, ctx) {
      console.log("eve response completed", {
        sessionId: ctx.session.id,
      });
    },
  },
});
```

`onMessage` must return an auth result. Return `title` alongside `auth` to set the title when the dispatch creates a session or sends the first message to a prewarmed session. A successful canonical eve HTTP message always dispatches and therefore always produces or continues a session.

## Clients

The browser side of this API lives in the [Frontend](../guides/frontend/overview) docs, where `useEveAgent` drives the eve channel from React UI.

For scripts, server-to-server calls, evals, tests, and custom clients, use the [Client SDK](../guides/client/overview). It wraps the ID-addressed session routes, stream cursor, and reconnect loop.

## What to read next

- [Frontend](../guides/frontend/overview): drive the eve channel from browser UI with `useEveAgent`
- [Client SDK](../guides/client/overview): call the eve channel from TypeScript
- [Auth & route protection](../guides/auth-and-route-protection): the route auth policy
- [Sessions, runs & streaming](../concepts/sessions-runs-and-streaming): the routes this channel exposes
