The local server started by kimi web exposes two programmatic surfaces: a REST API (/api/v1, plus /api/v2/sessions) and a WebSocket event stream (/api/v1/ws). This page is the protocol reference for both. For how to start the server and its command-line options, see the kimi command reference; for an end-to-end walkthrough, see Local server and API.
The complete request/response schema of every endpoint is owned by the server's live specification documents: GET /openapi.json (OpenAPI) and GET /asyncapi.json (AsyncAPI). Both require authentication.
::: warning
The REST and WebSocket APIs described on this page are experimental: interface stability is not guaranteed, and endpoints, fields, and event types may change in any release. When integrating, rely on the /openapi.json and /asyncapi.json documents served by your version.
:::
The default address is http://127.0.0.1:58627. When the port is taken, the server retries with the next port (up to 100 times); use --port / --host to change the bind. Multiple instances can coexist under the same home directory; running instances register under ~/.kimi-code/server/instances/.
All /api/* paths (including /openapi.json and /asyncapi.json) require the bearer token, except:
OPTIONSpreflight requestsGET /api/v1/healthz(liveness probe)- Static web assets (non-
/api/paths)
How to carry it: REST uses the Authorization: Bearer <token> header; the WebSocket upgrade accepts the same header or the subprotocol kimi-code.bearer.<token>. Token generation and rotation are covered in Local server and API: Authentication.
Failed authentication returns HTTP 401 with envelope code 40101. On non-loopback binds, a source that fails authentication 10 times within 60 seconds is banned for 60 seconds, during which every request gets HTTP 429 (code 42901).
Every JSON response is wrapped in a uniform envelope:
{
"code": 0,
"msg": "success",
"data": {},
"request_id": "01JZX4A6E7M8V0R3Q0N2K2M5Q9"
}code: the business outcome;0means success. See the error-code bands below.data: the payload on success. Note that some "error" envelopes also carry a non-nulldata— for example, resolving an already-resolved approval returns40902withdata.resolvedset tofalse— so clients should checkcodefirst, thendata.request_id: a ULID for this request. Clients may supply one via theX-Request-Idheader; invalid values are regenerated by the server.
The HTTP status is almost always 200; the business outcome lives in code. Exceptions:
| Situation | HTTP status |
|---|---|
| Authentication failure / rate limit | 401 / 429 |
| Provider created, provider catalog imported | 201 |
| Provider deleted | 204 |
| Binary/streaming endpoints | 206 (Range) / 304 (ETag unchanged) where supported — capabilities differ per endpoint, see Binary and streaming endpoints |
GET /api/v1/files/{file_id} download errors |
real 404 / 500 (still carrying an envelope body) |
The 201 responses still carry the standard envelope (code 0) — only the status line follows the REST convention for resource creation. A 204 response has no body by definition, so a successful delete is reported by the status code itself.
Error codes are grouped by band:
| Band | Meaning | Examples |
|---|---|---|
0 |
Success | |
400xx |
Bad request | 40001 validation failed (details lists each field), 40003 provider is OAuth-managed |
401xx |
Auth and readiness | 40101 unauthorized, 40110 no provider configured, 40113 model not resolved |
404xx |
Not found | 40401 session, 40408 MCP server, 40409 file path |
409xx |
State conflict | 40901 session busy, 40902 approval already resolved, 40922 page conditions mismatch page_token |
410xx |
Expired | 41001 approval timed out, 41002 question timed out, 41003 temporary file expired |
413xx |
Size or boundary exceeded | 41302 file read over 10 MB, 41304 path escapes the session directory |
429xx |
Rate limited | 42901 auth-failure ban, 42902 too many fs watches |
500xx |
Server internal error | 50001 uncaught exception, 50003 persistence failure |
6xxxx / 7xxxx / 8xxxx |
Tool runtime / LLM provider / MCP passthrough errors; msg carries the upstream text |
List endpoints come in two styles:
- Cursor style:
before_id/after_id(mutually exclusive) pluspage_size(1–100), responding with{ items, has_more }. Used by the session list, message list, transcript, and others. page_token: an opaque token (bound to a fingerprint of the query conditions), used byPOST /api/v1/searchandGET /api/v2/sessions. Changing any query condition mid-pagination invalidates the token: v2 returns40922, search returns40001.GET /api/v2/sessionsalso offers a statelesspagepage-number mode as an alternative.
Endpoints are grouped by resource below. A :{action} suffix in a path is the action convention — POST to path:action on a single resource for non-CRUD operations (such as :fork and :archive on a session).
| Method and path | Description |
|---|---|
GET /api/v1/healthz |
Liveness probe; auth-exempt |
GET /api/v1/meta |
Server version, capability map, server_id, experimental flags |
POST /api/v1/shutdown |
Graceful shutdown (replies 200 first); mounted only on loopback binds |
| Method and path | Description |
|---|---|
GET /api/v1/auth |
Auth readiness snapshot |
POST /api/v1/oauth/login |
Start the OAuth device-code login flow |
GET /api/v1/oauth/login |
Poll the login flow state |
DELETE /api/v1/oauth/login |
Cancel a pending login flow |
POST /api/v1/oauth/logout |
Log out the managed provider |
GET /api/v1/oauth/usage |
Plan usage and limits |
GET /api/v1/oauth/userinfo |
Account profile |
| Method and path | Description |
|---|---|
GET /api/v1/config |
Read the global config (secret fields redacted) |
POST /api/v1/config |
Merge-patch the config; broadcasts event.config.changed |
| Method and path | Description |
|---|---|
GET /api/v1/models |
List configured model aliases |
POST /api/v1/models/{model_id}:set_default |
Set the global default model |
GET /api/v1/providers |
List providers |
POST /api/v1/providers |
Create a provider (201) |
GET /api/v1/providers/{provider_id} |
Read a provider (reveals the stored key) |
PUT /api/v1/providers/{provider_id} |
Replace a provider |
DELETE /api/v1/providers/{provider_id} |
Delete a provider (204) |
POST /api/v1/providers/{provider_id}:refresh |
Refresh one provider's model metadata |
POST /api/v1/providers:{action} |
Collection actions: refresh / refresh_oauth / import_catalog / import_registry |
GET /api/v1/catalog/providers |
Browse the models.dev directory (server-proxied) |
GET /api/v1/catalog/providers/{catalog_id} |
Read one directory entry |
| Method and path | Description |
|---|---|
POST /api/v1/sessions |
Create a session (requires workspace_id or metadata.cwd) |
GET /api/v1/sessions |
List sessions; cursor pagination with filters such as busy and archived_only |
GET /api/v1/sessions/{session_id} |
Read one session |
GET /api/v1/sessions/{session_id}/profile |
Read the session profile |
POST /api/v1/sessions/{session_id}/profile |
Update title, metadata, agent config |
POST /api/v1/sessions/{session_id}:{action} |
Session actions: fork / compact / undo / abort / btw / archive / restore |
GET /api/v1/sessions/{session_id}/children |
List child sessions |
POST /api/v1/sessions/{session_id}/children |
Create a child session (fork with a tag) |
GET /api/v1/sessions/{session_id}/status |
Realtime status rollup |
GET /api/v1/sessions/{session_id}/goal |
Current goal snapshot (null when none) |
GET /api/v1/sessions/{session_id}/warnings |
Session-level warnings |
POST /api/v1/sessions/{session_id}/export |
Export the session with diagnostics (zip stream, not enveloped) |
GET /api/v1/sessions/{session_id}/snapshot |
Full snapshot for client rebuilds (with as_of_seq and epoch) |
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/messages |
Page messages (before_id / after_id / role) |
GET /api/v1/sessions/{session_id}/messages/{message_id} |
Read one message |
GET /api/v1/sessions/{session_id}/transcript |
Turn-paged transcript (requires agent_id); global state rides along unpaginated |
GET /api/v1/sessions/{session_id}/transcript/ops |
Op-batch catch-up (since_seq); complete: false means a full refresh is needed |
GET /api/v1/sessions/{session_id}/transcript/user-messages |
Turn-opening user inputs, unpaginated |
GET /api/v1/sessions/{session_id}/transcript/plan |
ExitPlanMode plan content, path, and review outcome |
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/prompts |
Active and queued prompts |
POST /api/v1/sessions/{session_id}/prompts |
Submit a prompt (content-part array, optional model / permission-mode overrides) |
POST /api/v1/sessions/{session_id}/prompts:steer |
Steer queued prompts into the active turn |
POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort |
Abort a running prompt |
POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer |
Steer one queued prompt |
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/approvals |
List approval requests (filter with status=pending) |
POST /api/v1/sessions/{session_id}/approvals/{approval_id} |
Resolve an approval |
GET /api/v1/sessions/{session_id}/questions |
List questions |
POST /api/v1/sessions/{session_id}/questions/{question_id} |
Answer a question |
POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss |
Dismiss a question |
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/tasks |
List background tasks |
GET /api/v1/sessions/{session_id}/tasks/{task_id} |
Read a task (optional output preview) |
POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel |
Cancel a task |
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/skills |
Per-session skill catalog |
GET /api/v1/workspaces/{workspace_id}/skills |
Session-less skill catalog for a workspace |
POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate |
Activate a skill (starts a turn) |
GET /api/v1/tools |
List tools of the effective agent |
GET /api/v1/mcp/servers |
List MCP servers |
POST /api/v1/mcp/servers/{mcp_server_id}:restart |
Restart an MCP server |
PTY terminal endpoints; mounted only on loopback binds.
| Method and path | Description |
|---|---|
GET /api/v1/sessions/{session_id}/terminals |
List terminals |
POST /api/v1/sessions/{session_id}/terminals |
Create a terminal |
GET /api/v1/sessions/{session_id}/terminals/{terminal_id} |
Read a terminal (including scrollback) |
POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close |
Close a terminal |
| Method and path | Description |
|---|---|
GET /api/v1/workspaces |
List registered workspaces |
POST /api/v1/workspaces |
Register a workspace (idempotent on the root path) |
PATCH /api/v1/workspaces/{workspace_id} |
Rename |
DELETE /api/v1/workspaces/{workspace_id} |
Unregister (keeps on-disk content) |
GET /api/v1/workspaces/{workspace_id}/trust |
Read the trust state |
POST /api/v1/workspaces/{workspace_id}/trust |
Grant trust |
POST /api/v1/workspaces/{workspace_id}/untrust |
Revoke trust |
In-session file operations go through POST /api/v1/sessions/{session_id}/fs:{action} with JSON bodies; actions are list / read / list_many / stat / stat_many / mkdir / search / grep / git_status / diff / open / open-in / reveal. In addition:
| Method and path | Description |
|---|---|
POST /api/v1/workspace/fs:search |
Session-less workspace search (the body carries the workspace reference) |
GET /api/v1/sessions/{session_id}/fs/{path}:download |
Download a session file (binary, see below) |
GET /api/v1/fs:browse |
List host directories (folder picker) |
GET /api/v1/fs:home |
The user's home directory and recent workspaces |
GET /api/v1/fs:content |
Raw bytes of any host file (gated only by the token — be careful when exposing the port) |
POST /api/v1/fs:mkdir |
Create a directory by absolute path |
| Method and path | Description |
|---|---|
POST /api/v1/files |
Multipart upload (file field, optional name and expires_in_sec); returns file metadata |
GET /api/v1/files/{file_id} |
Download (binary; errors use real HTTP statuses) |
DELETE /api/v1/files/{file_id} |
Delete |
| Method and path | Description |
|---|---|
POST /api/v1/search |
Cross-session full-text search; mode is terms (default) or literal (exact substring); page_token pagination |
GET /api/v1/connections |
List live WebSocket connections |
GET /api/v2/sessions |
Next-generation session list, see below |
POST /api/v2/sessions:archive |
Batch-archive sessions, see below |
POST /api/v2/sessions:restore |
Batch-restore archived sessions, see below |
/api/v1/debug/* |
Reflection debug RPC; mounted only with --debug-endpoints on loopback, not a stable protocol |
A next-generation session query for list views — filtering, sorting, and field groups all travel in query parameters:
| Parameter | Description |
|---|---|
workspace.id |
Filter by workspace; repeatable |
activity.status |
Filter by activity status: running / approval / question / failed / idle; repeatable |
meta.updated_after |
Only sessions updated after this time (epoch milliseconds) |
meta.updated_before |
Only sessions updated before this time (epoch milliseconds) |
meta.archived |
true / false (default) / all |
sort |
meta.updated_at_desc (default) / meta.updated_at_asc / meta.created_at_desc |
include |
Comma-separated extra field groups; currently only git (branch and PR info, deduplicated per directory and cached for 60 seconds) |
fields |
Comma-separated item projection; currently only id,archived, trimming each item to { id, archived } (select-all-matching flows). Not combinable with include=git (40001) |
page_size |
1–100, default 50; up to 10000 with the id,archived projection |
page_token |
Pagination token from the previous page |
page |
Stateless 1-based page number; mutually exclusive with page_token (40001 when combined) |
Every response item carries the workspace, meta, and activity groups, plus git when include=git — or just { id, archived } under fields=id,archived. Every page additionally carries total, the size of the filtered set. The page token binds the first page's query conditions (including the projection); changing them mid-pagination returns 40922. page mode is a stateless alternative for jumping to arbitrary pages: every request is an independent snapshot, no token is minted, and next_page_token is always null.
Batch archive/restore for session-management views. The body is { "ids": ["session_..."] } — non-empty, at most 5000 unique ids (duplicates collapse). Live sessions go through the full lifecycle; cold sessions are patched on disk without being loaded.
Only a body validation failure fails the whole request (40001). Otherwise the response is per-item: data.results keeps the input order with { id, ok } or { id, ok: false, error } (an unknown id reports 40401 in its own item), plus succeeded / failed counts.
{
"code": 0,
"msg": "success",
"data": {
"results": [
{ "id": "session_a", "ok": true },
{ "id": "session_b", "ok": false, "error": { "code": 40401, "message": "session session_b does not exist" } }
],
"succeeded": 1,
"failed": 1
},
"request_id": "req_..."
}The only endpoint is ws://<host>:<port>/api/v1/ws; authentication happens at the upgrade request (see Authentication above). Once connected, the server immediately sends server_hello:
{
"type": "server_hello",
"timestamp": "2026-01-01T00:00:00.000Z",
"payload": {
"ws_connection_id": "conn_01JZX4...",
"protocol_version": 2,
"max_event_buffer_size": 1000,
"capabilities": { "event_batching": false, "compression": false }
}
}Note that the server never sends heartbeats and never disconnects an idle connection — keepalive and reconnection are the client's job.
Clients send JSON frames { "type", "id"?, "payload" }; every request frame gets an acknowledgement { "type": "ack", "id", "code", "msg", "payload" }, where code 0 means success.
| Frame | payload | Description |
|---|---|---|
subscribe |
{ session_ids, cursors?, agent_filter? } |
Subscribe to session events; with cursors (per-session {seq, epoch}) the server replays missed durable events |
unsubscribe |
{ session_ids } |
Drop session subscriptions |
subscribe_v2 |
{ session_id, transcript, transcript_since? } |
Subscribe to transcript streams (the only transcript channel); transcript sets per-agent grades |
unsubscribe_v2 |
{ session_id, agent_ids? } |
Detach transcript streams; omitting agent_ids means the whole session |
watch_fs_add / watch_fs_remove |
{ session_id, paths, recursive? } |
Subscribe to / unsubscribe from file-change notifications (event.fs.changed) |
client_hello |
{ client_id } |
Handshake frame; the remaining fields are legacy compatibility |
Event frames look like { "type", "seq", "epoch"?, "volatile"?, "offset"?, "session_id"?, "timestamp", "payload" }, where type is the event type itself. Two delivery scopes:
- Global events: sent to every established connection, no subscription needed —
session.meta.updated,event.session.created,event.session.work_changed,event.session.status_changed,event.workspace.*,event.config.*. - Session events: sent only to connections subscribed to that session, subject to
agent_filter. Main families:
| Family | Main events |
|---|---|
| Turns | turn.started, turn.ended, turn.step.started / completed / interrupted / retrying |
| Streaming text | assistant.delta, thinking.delta (carry offset for alignment) |
| Tool calls | tool.call.started, tool.call.delta, tool.progress, tool.result |
| Interactions | event.approval.requested / resolved, event.question.requested / answered / dismissed |
| Subagents | subagent.spawned / started / suspended / completed / failed |
| Background | task.started / terminated, shell.started / output / completed |
| Misc | compaction.*, skill.activated, goal.updated, prompt.*, error, warning |
Events also split into durable and volatile: durable events carry a strictly increasing seq, are journaled, and can be replayed; volatile events (the *.delta family, tool.progress, shell.*, and similar) are marked volatile: true and never replayed. When consuming a volatile text stream, compare offset (the cumulative character offset within the turn) against your locally accumulated text: below the local length means a duplicate frame; above means a gap that needs snapshot recovery.
After reconnecting, pass each session's last applied {seq, epoch} in subscribe's cursors; the server replays the gap. If you fall more than the buffer (1000 events) behind, or the cursor is no longer valid, you get resync_required instead. In that case, call GET /api/v1/sessions/{session_id}/snapshot for a full snapshot (with as_of_seq and epoch), then subscribe again with the fresh cursor.
subscribe_v2's transcript field sets a per-agent grade: off / turn / block / delta (the "*" key sets the default grade), with higher grades pushing finer detail. An agent with a non-off grade receives two frame types: transcript.reset (a baseline snapshot; history pages in over REST) and transcript.ops (incremental op batches with a per-agent strictly increasing seq). The agent's legacy events are suppressed on that connection and carried by transcript frames instead. After a disconnect, resume with transcript_since; when the server's op journal cannot cover the gap (REST catch-up returns complete: false), do a full refresh. The REST counterparts are GET .../transcript (turn-paged) and GET .../transcript/ops?since_seq= (op-batch catch-up).
The following endpoints stream binary bodies instead of a JSON payload. Their HTTP capabilities differ per endpoint:
| Method and path | Description | Range (206) | ETag / 304 |
|---|---|---|---|
GET /api/v1/files/{file_id} |
Download an uploaded file | Yes | No (sends an etag header but ignores If-None-Match) |
GET /api/v1/sessions/{session_id}/fs/{path}:download |
Download a session workspace file | Yes | Yes |
GET /api/v1/fs:content |
Raw bytes of any host file (gated only by the token — be careful when exposing the port) | Yes | Yes |
POST /api/v1/sessions/{session_id}/export |
Export the session with diagnostics (zip stream) | No | No |
Error semantics differ as well: GET /api/v1/files/{file_id} answers lookup and storage failures with real 404 / 500 statuses (parameter validation still uses the HTTP 200 envelope), while the other three report every failure through the standard response envelope — clients must keep checking the envelope code on those endpoints.
- Local server and API — startup, authentication, and the end-to-end calling flow
- kimi command — all
kimi webcommand-line options