Appearance
HTTP API reference
Every Agent SDK host speaks the same stable HTTP API. In the default multi-agent layout each agent is namespaced under its slug (/<slug>/v1/session, /<slug>/playground), with host-level routes at the root. With --mode single, one agent serves the same surface unslugged (/v1/*).
Unless noted otherwise, routes run the agent's HTTP auth chain: the default is localDevStrict() (loopback only), replaced by bearerAuth under --bearer-token or allowAll() under --allow-anonymous. Session routes also require the caller to be the session's owner (403 otherwise). Errors return JSON { ok: false, error: "<code>", message? } with a matching HTTP status.
Host-level routes (multi-agent mode)
These routes live at the host root, above any agent. The two index routes exist only while the playground is enabled (--no-playground removes them) and run no auth. The documentation site is mounted in both layouts and removed by --no-docs.
| Route | What it does |
|---|---|
GET / | A web index of every mounted agent, linking to playgrounds (playground only) |
GET /v1/agents | The JSON index of mounted agents (playground only, no auth) |
GET /docs, GET /docs/* | This documentation, served as a static site (both layouts, no auth) |
GET /v1/health | Host-level liveness, no auth; made for ALB/ECS checks |
POST /v1/webhooks/github | Loopback-only trigger endpoint that fans a GitHub-shaped payload out to every mounted GitHub channel (used by local tooling) |
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"}The response returns as soon as the message is accepted; follow the stream for progress. The continuation token is the follow-up credential, and playgroundUrl deep-links the session in the playground.
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."}'Works for any chat session, including ones created by custom channels. Each accepted follow-up rotates the token, and the response carries the new one. Sending to a busy session interrupts the in-flight turn, waits for it to settle, then sends; when the turn can't be interrupted (for example, concurrent follow-ups racing), the request returns 409 session_busy.
Expect 409 on a stale token, an uninterruptible busy session, or a task/schedule session (those aren't followable), and 403 when the caller isn't the session owner.
Stream a session
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'One NDJSON event per line, from startIndex, then following live. The default is 0: omitting the parameter replays the entire recorded stream before following. Pass the last index you've seen plus one to resume without duplicates. The stream is durable and reconnectable. For the vocabulary, see Sessions.
GET /v1/session/:sessionId/events returns the same content as a one-shot dump with no live follow.
Stop and list
POST /v1/session/:sessionId/stop interrupts the in-flight turn without sending a new message. GET /v1/sessions lists sessions owned by the calling principal. Under serve --dev on loopback it includes all sessions, which is how webhook and schedule sessions show up in the playground.
Session cost
GET /v1/session/:sessionId/cost returns the session's cost report: per-turn token usage and the engine's estimated cost, folded from turn.completed events. It runs the same owner check as the other session routes and returns 404 for an unknown session. The agent-sdk cost command reports the same data.
Approvals
Two routes list and resolve parked tool calls.
| Route | What it does |
|---|---|
GET /v1/session/:sessionId/approvals | Pending human-in-the-loop tool approvals |
POST /v1/session/:sessionId/approvals/:callId | Resolve one: {"decision":"approve"} or {"decision":"deny"} |
For the lifecycle, see Human-in-the-loop.
Call a tool directly
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}It runs an authored server tool in-process: schema-validated, no model turn. An optional "sessionId" in the body runs it inside an existing session and records it on that session's stream (409 session_busy while a turn runs). Agent-execution tools are rejected with 400, and unknown tools with 404 and the list of available names. For the semantics, see Tools.
Discovery and meta
Five read-only routes describe the running agent.
| Route | What it does |
|---|---|
GET /v1/info | The manifest snapshot: model, tools, skills, MCP connections, subagents, channels and routes (with schemas), schedules, hooks, A/B experiments, diagnostics. Always the bare project-info object; agent-sdk info --json wraps the same data per slug in { agents: [...] } |
GET /v1/health | Per-agent liveness, no auth |
GET /v1/meta | SPA bootstrap: agent name, dev flag, base path (no auth) |
GET /v1/logs?after=N | Recent server log lines from the ring buffer, with a polling cursor |
GET /v1/abs | Live A/B metrics: per-session assignments and aggregate arm totals folded from durable event streams (config reports maxPlaygroundSessions / durableSamples / durableSnapshots from agent/ab.config.ts) |
Artifacts
Two routes read durable artifacts tagged by ctx.artifacts or tag_artifact. See Artifacts.
| Route | What it does |
|---|---|
GET /v1/artifacts | List artifacts as { artifacts }, newest-updated first. Filter with ?kind=, ?sessionId=, and ?limit= (a positive integer) |
GET /v1/artifacts/:id/content | Download one artifact's file or blob payload. Served as an attachment, never rendered inline; 404 when the artifact is unknown or carries no content |
Session ownership applies the same way as GET /v1/sessions: under serve --dev on loopback (or --allow-anonymous) the list spans all principals, while bearer or custom channel auth keeps strict per-principal isolation.
Custom channel routes
Authored routes mount under /v1/channels/<id> with the methods, paths, and Zod schemas the channel declared (a POST /<slug>/v1/channels/drive route, say). Bodies are validated before handlers run (400 on schema violations), and each channel's auth chain applies. The GitHub channel verifies X-Hub-Signature-256 when a secret is configured. See Channels.
MCP endpoint
/v1/mcp serves the Model Context Protocol over streamable HTTP (stateless; POST carries the protocol, and GET/DELETE return spec-compliant 405s). The tools are ask (delegate a message, bounded waits), check (poll a running session), and call_tool (deterministic server-tool passthrough, present when the agent has server tools). The route runs the same auth chain as the session API. See Agent-to-agent.
/v1/mcp/tools is a second stateless MCP endpoint exposing only the agent's deterministic server tools. Hosted cloud turns call back into it through the URL configured by serve --cloud-tools-url. Unlike /v1/mcp, it runs the CLI-level auth chain (loopback, bearer, or anonymous), not any authored channel auth.
POST /v1/cursor-account/:connection/mcp is the bridge for defineConnection({ cursorAccount: true }) connections. The runtime calls it with a per-boot bearer secret; it never joins the public auth chain, and an unknown connection name returns 404.
Playground eval routes
Always registered (including production / non---dev serves). The playground Evals tab uses these:
| Route | What it does |
|---|---|
GET /v1/dev/evals | List discovered eval datapoints and project config as { evals, config } (config includes maxPlaygroundRuns, durableRuns) |
GET /v1/dev/evals/runs | List recent run snapshots (newest first) as { runs, activeRunId? } for playground rehydrate |
POST /v1/dev/evals/runs | Start an eval run ({filterIds?, tags?}); 202 with a snapshot (runId is the Eval ID), 404 when nothing matches, 409 when one is running |
GET /v1/dev/evals/runs/:runId | Poll a run's progress |
POST /v1/dev/evals/runs/:runId/cancel | Cancel a running batch; 200 with snapshot, 404 unknown, 409 when not running |
Eval runs are asynchronous. Poll the run route for case progress and the final completed or failed status. Batch errors appear on the snapshot returned by the poll. Entries within filterIds and tags use OR semantics. When both fields are present, a case must match one entry from each field. Listed runs persist across restarts whenever agent/storage.ts provides an evals table or a KV core with delete and list (the table is derived — see Storage); otherwise they are process-memory only (capped by maxPlaygroundRuns).
Dev-mode routes
These routes exist only under serve --dev.
| Route | What it does |
|---|---|
POST /v1/dev/schedules/:scheduleId | Dispatch a schedule by hand, exactly once, through the production path. 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 are the only way they run, which keeps iteration deterministic.
Platform timer routes
POST /v1/internal/schedules/:scheduleId/fire and POST /v1/internal/reminders/:reminderId/fire exist only under serve --no-control-plane, where the host runs no schedule or reminder clocks of its own. Cursor hosting starts engines this way and fires timed work through them. They admit only requests carrying the platform's x-agent-serve-timed-work marker, which the alias proxy strips from external traffic, so webhook and playground callers can never reach them.
Playground assets
GET /playground and GET /playground/assets/:file serve the static SPA bundle (omitted with --no-playground). The playground calls the JSON API above and has no privileged surface.
Status codes
Error responses use a small, consistent set of status codes.
| Code | Meaning here |
|---|---|
400 | Schema-invalid body or query, agent-execution tool called on the host, malformed request |
401 | No auth policy admitted the request |
403 | Authenticated, but not the session owner |
404 | Unknown session, tool, schedule, reminder, or eval run; no eval datapoints match a run request |
405 | Wrong method (GET on the MCP endpoint, say) |
409 | Stale continuation token, busy session (session_busy), a non-followable task session, or an eval run already in progress |
202 | Accepted for background work (GitHub { task } hooks, eval runs) |
What's next
Continue with these pages:
- Sessions and streaming: the handles and events these routes traffic in
- Channels: authoring your own routes
- Deployment: auth on real hosts