Skip to content

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.

RouteWhat it does
GET /A web index of every mounted agent, linking to playgrounds (playground only)
GET /v1/agentsThe 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/healthHost-level liveness, no auth; made for ALB/ECS checks
POST /v1/webhooks/githubLoopback-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.

RouteWhat it does
GET /v1/session/:sessionId/approvalsPending human-in-the-loop tool approvals
POST /v1/session/:sessionId/approvals/:callIdResolve 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.

RouteWhat it does
GET /v1/infoThe 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/healthPer-agent liveness, no auth
GET /v1/metaSPA bootstrap: agent name, dev flag, base path (no auth)
GET /v1/logs?after=NRecent server log lines from the ring buffer, with a polling cursor
GET /v1/absLive 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.

RouteWhat it does
GET /v1/artifactsList artifacts as { artifacts }, newest-updated first. Filter with ?kind=, ?sessionId=, and ?limit= (a positive integer)
GET /v1/artifacts/:id/contentDownload 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:

RouteWhat it does
GET /v1/dev/evalsList discovered eval datapoints and project config as { evals, config } (config includes maxPlaygroundRuns, durableRuns)
GET /v1/dev/evals/runsList recent run snapshots (newest first) as { runs, activeRunId? } for playground rehydrate
POST /v1/dev/evals/runsStart 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/:runIdPoll a run's progress
POST /v1/dev/evals/runs/:runId/cancelCancel 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.

RouteWhat it does
POST /v1/dev/schedules/:scheduleIdDispatch a schedule by hand, exactly once, through the production path. Returns {scheduleId, sessionIds}
GET /v1/dev/remindersList reminders
POST /v1/dev/reminders/:reminderIdFire 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.

CodeMeaning here
400Schema-invalid body or query, agent-execution tool called on the host, malformed request
401No auth policy admitted the request
403Authenticated, but not the session owner
404Unknown session, tool, schedule, reminder, or eval run; no eval datapoints match a run request
405Wrong method (GET on the MCP endpoint, say)
409Stale continuation token, busy session (session_busy), a non-followable task session, or an eval run already in progress
202Accepted for background work (GitHub { task } hooks, eval runs)

What's next

Continue with these pages: