Server API
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.
Conventions
Address
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/.
Authentication
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).
Response envelope
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
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 |
Pagination
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.
REST endpoints
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).
Server and metadata
| 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 |
Login and usage
| 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 |
Config
| 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 |
Models and providers
| 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 |
Sessions
| 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) |
Messages and transcript
| 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 |
Prompts
| 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 |
Approvals and questions
| 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 |
Background tasks
| 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 |
Skills, tools, and MCP
| 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 |
Terminals
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 |
Workspaces
| 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 |
File system
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 |
File uploads
| 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 |
Global search and misc
| 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 |
/api/v1/debug/* | Reflection debug RPC; mounted only with --debug-endpoints on loopback, not a stable protocol |
GET /api/v2/sessions
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.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) |
page_size | 1–100, default 50 |
page_token | Pagination token from the previous page |
Every response item carries the workspace, meta, and activity groups, plus git when include=git. The page token binds the first page's query conditions; changing them mid-pagination returns 40922.
WebSocket protocol
Connect
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.
Control frames
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 |
Events
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.
Reconnect and 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.
Transcript protocol
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).
Binary and streaming endpoints
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.
Next steps
- Local server and API — startup, authentication, and the end-to-end calling flow
- kimi command — all
kimi webcommand-line options