# HTTP API

Agent SDK hosts expose one public HTTP surface. In the default
multi-agent layout, each agent uses `/<slug>/v1/*`; `--mode single` serves the
same routes at `/v1/*`. Routes use the agent's HTTP auth chain, and
session-owned resources return `403` to another principal.

Unless a section says otherwise, the default auth policy is
`localDevStrict()`. `--bearer-token` replaces it with bearer auth, and
`--allow-anonymous` replaces it with anonymous access. Built-in JSON
routes use `{ ok: false, error: "<code>", message? }` for errors, and MCP
uses JSON-RPC. Custom channel routes answer hosted callers with an empty
`202`; see [Call custom channel routes](#call-custom-channel-routes).

## Host routes

These routes live at the host root in multi-agent mode. The index routes
exist only when the playground is enabled.

| Route | Contract |
| --- | --- |
| `GET /` | HTML index of mounted agents; no auth |
| `GET /v1/agents` | JSON index of mounted agents; no auth |
| `GET /docs`, `GET /docs/*` | Documentation site in either layout; no auth |

`--no-playground` removes the two index routes. `--no-docs` removes the
documentation site.

## Start a session

`POST /v1/session` opens a durable conversation.

```bash
curl -X POST http://127.0.0.1:3000/<slug>/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"What can you do?"}'
# {"ok":true,"sessionId":"ses_…","continuationToken":"http:…",
#  "playgroundUrl":"…?sessionId=ses_…","traceUrl":"…/v1/session/ses_…/events"}
```

Once accepted, the response returns `sessionId` for inspection and
`continuationToken` for follow-ups; follow the stream for progress.

| Body field | Contract |
| --- | --- |
| `message` | Required user message |
| `title` | Display title |
| `dryRun` | Run read tools and stub write tools |
| `asOf` | ISO-8601 instant with a timezone; sets `ctx.now()` and rejects omitted, relative, or later declared tool time arguments. Invalid values return `400` |
| `workspaceFiles` | Relative files added to the session workspace |
| `purpose` | Use `"eval"` to mark regression traffic |

## Send a follow-up

`POST /v1/session/:sessionId` continues an existing conversation.

```bash
curl -X POST http://127.0.0.1:3000/<slug>/v1/session/ses_… \
  -H 'content-type: application/json' \
  -d '{"continuationToken":"http:…","message":"Make it shorter."}'
```

The route accepts any chat session, including one created by a custom
channel. Each accepted follow-up rotates the continuation token and
returns the replacement. A message sent to a busy session interrupts
the active turn before starting.

The route returns `409` for a stale token or task session, and `403`
when the caller doesn't own the session.

## Stream or replay session events

`GET /v1/session/:sessionId/stream` is the live NDJSON feed.

```bash
curl -N 'http://127.0.0.1:3000/<slug>/v1/session/ses_…/stream?startIndex=0'
```

The route replays one event per line from `startIndex`, then follows new
events. The default `0` replays the full stream. To reconnect without
duplicates, pass the last index you received plus one.

`GET /v1/session/:sessionId/events` returns a one-shot NDJSON dump.
Pass `?format=json` for `{ sessionId, events, playgroundUrl }`. See
[Stream events](/docs/reference/sessions.md#stream-events) for the event vocabulary.

## Manage sessions

| Route | Contract |
| --- | --- |
| `POST /v1/session/:sessionId/stop` | Interrupt the active turn without sending another message |
| `GET /v1/sessions` | List sessions owned by the caller |
| `GET /v1/session/:sessionId/cost` | Return per-turn token usage and estimated cost |

On loopback under `serve --dev`, the session list includes every
principal so webhook and schedule sessions appear in the playground.
The cost route returns `404` for an unknown session.

## Call a tool without a model turn

`POST /v1/tools/:toolName` runs a server tool with no model turn.

```bash
curl -X POST http://127.0.0.1:3000/<slug>/v1/tools/inspect_pr \
  -H 'content-type: application/json' \
  -d '{"input":{"prUrl":"https://github.com/acme/checkout/pull/42"}}'
# {"ok":true,"toolName":"inspect_pr","callId":"tool_inspect_pr_…",
#  "isError":false,"result":{…},"durationMs":12}
```

| Body field | Contract |
| --- | --- |
| `input` | Tool input; defaults to `{}` |
| `sessionId` | Bind the call to an existing session |
| `continuationToken` | Bind the call by its wire continuation token; mutually exclusive with `sessionId` |

Omit both identifiers for an unbound call. Agent-execution tools return
`400`, and an unknown name returns `404` with the available names. See
[Tools](/docs/reference/tools.md#call-a-tool-without-a-model-turn) for session
binding, busy-session rules, and error codes.

## Discovery routes

These read-only routes describe the running agent.

| Route | Contract |
| --- | --- |
| `GET /v1/info` | Return the discovered model, tools, skills, connections, subagents, channels, schedules, hooks, and diagnostics |
| `GET /v1/tools` | Return live server tools and advertised MCP tools as `{ name, title?, source? }`; bind session-specific listings with `session` or `continuationToken` |
| `GET /v1/tools/:name` | Return one tool's description, execution mode, effect rules, schemas, and source |
| `GET /v1/logs?after=N` | Return recent server logs and the next polling cursor |

An unknown tool name returns `404` with available names. If an MCP
connection can't list its tools, the catalog skips that connection and
includes it in `connectionErrors`.

## List and download artifacts

| Route | Contract |
| --- | --- |
| `GET /v1/artifacts` | Return `{ artifacts }`, newest-updated first; filter with `kind`, `sessionId`, and a positive `limit` |
| `GET /v1/artifacts/:id/content` | Download an artifact's file or blob as an attachment; return `404` when no content exists |

The list follows the same ownership rules as `GET /v1/sessions`. See
[Artifacts](/docs/reference/artifacts.md) for tagging and record fields.

## Call custom channel routes

Authored routes mount under `/v1/channels/<id>` with their declared
methods and paths, and each channel's auth chain applies. A hosted
caller receives `202 Accepted` with an empty body, never the handler's
response. See [Route responses](/docs/reference/channels.md#route-responses).

## MCP endpoint

Both MCP routes use stateless streamable HTTP. Send protocol requests
with `POST`; `GET` and `DELETE` return `405`.

| Route | Tools and auth |
| --- | --- |
| `/v1/mcp` | `ask`, `check`, and `call_tool` when server tools exist; uses the session API auth chain |
| `/v1/mcp/tools` | Deterministic server tools only; uses the CLI-level loopback, bearer, or anonymous auth chain |

See [MCP connections](/docs/reference/connections.md#peer-mcp-connection) to connect
one agent to another.

## Playground eval routes

The playground Evals tab and remote eval commands use these routes:

| Route | Contract |
| --- | --- |
| `GET /v1/dev/evals` | List discovered cases and eval config |
| `GET /v1/dev/evals/runs` | List recent run snapshots, newest first |
| `POST /v1/dev/evals/runs` | Start `{ filterIds?, tags?, timeoutMs?, verbose? }`; return `202`, `404` for no match, or `409` while another run is active |
| `GET /v1/dev/evals/runs/:runId` | Return progress and the final snapshot |
| `POST /v1/dev/evals/runs/:runId/cancel` | Cancel an active run; return `404` when unknown or `409` when no longer running |

Poll the run route until its status is `completed`, `failed`, or
`cancelled`. Entries within `filterIds` and `tags` use OR semantics;
when both fields are present, a case must match each group.

## Dev-mode routes

These routes exist only under `serve --dev`.

| Route | Contract |
| --- | --- |
| `POST /v1/dev/schedules/:scheduleId` | Dispatch a schedule by hand, exactly once. Returns `{scheduleId, sessionIds}` |
| `GET /v1/dev/reminders` | List reminders |
| `POST /v1/dev/reminders/:reminderId` | Fire a reminder by hand |

Schedules and reminders never fire automatically in dev mode. These
routes run them manually.

## Playground assets

`GET /playground` and `GET /playground/assets/:file` serve the
playground. `--no-playground` removes both routes.

## Status codes

Built-in routes use these common status codes.

| Code | Meaning |
| --- | --- |
| `400` | Invalid body, query, tool input, or request shape |
| `401` | No auth policy admitted the request |
| `403` | The caller is authenticated but doesn't own the resource |
| `404` | A named resource doesn't exist, or an eval selection matches no cases |
| `405` | The route doesn't accept this method |
| `409` | The resource state rejects the request, such as a stale token or busy write |
| `202` | The request was accepted for asynchronous work |
| `500` | A write-effect tool can't create its session workspace (`workspace_unavailable`) |

## Related

- [Sessions](/docs/reference/sessions.md)
- [Channels](/docs/reference/channels.md)
- [Tools](/docs/reference/tools.md)
- [Deployment](/docs/deployment.md)
