HTTP API
Bot Development Kit (BDK) 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.
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.
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.
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.
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 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.
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 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 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.
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 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) |