docs(server): add local server guide and API reference (#2839)

* docs(server): add local server guide and API reference

* docs(server): qualify binary endpoint HTTP semantics
This commit is contained in:
Haozhe 2026-08-12 12:29:09 +08:00 committed by GitHub
parent 26ddc1d0fb
commit dc8db90cdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 928 additions and 2 deletions

View file

@ -56,6 +56,7 @@ const config = withMermaid(defineConfig({
{ text: '会话与上下文', link: '/zh/guides/sessions' },
{ text: '使用目标模式', link: '/zh/guides/goals' },
{ text: '在 IDE 中使用', link: '/zh/guides/ides' },
{ text: '本地服务与 API', link: '/zh/guides/server' },
],
},
],
@ -90,6 +91,7 @@ const config = withMermaid(defineConfig({
items: [
{ text: 'kimi 命令', link: '/zh/reference/kimi-command' },
{ text: 'kimi acp 子命令', link: '/zh/reference/kimi-acp' },
{ text: '服务 API', link: '/zh/reference/server-api' },
{ text: '内置工具', link: '/zh/reference/tools' },
{ text: '斜杠命令', link: '/zh/reference/slash-commands' },
{ text: '键盘快捷键', link: '/zh/reference/keyboard' },
@ -133,6 +135,7 @@ const config = withMermaid(defineConfig({
{ text: 'Sessions and Context', link: '/en/guides/sessions' },
{ text: 'Using Goals', link: '/en/guides/goals' },
{ text: 'Using in IDEs', link: '/en/guides/ides' },
{ text: 'Local Server and API', link: '/en/guides/server' },
],
},
],
@ -167,6 +170,7 @@ const config = withMermaid(defineConfig({
items: [
{ text: 'kimi Command', link: '/en/reference/kimi-command' },
{ text: 'kimi acp Subcommand', link: '/en/reference/kimi-acp' },
{ text: 'Server API', link: '/en/reference/server-api' },
{ text: 'Built-in Tools', link: '/en/reference/tools' },
{ text: 'Slash Commands', link: '/en/reference/slash-commands' },
{ text: 'Keyboard Shortcuts', link: '/en/reference/keyboard' },

View file

@ -121,6 +121,7 @@ Switches that control the behavior of subsystems such as telemetry, background t
| Variable | Purpose | Valid values |
| --- | --- | --- |
| `KIMI_DISABLE_TELEMETRY` | Disable anonymous telemetry reporting | `1`, `true`, `yes`, `y` (case-insensitive) |
| `KIMI_CODE_PASSWORD` | Set a parallel auth credential for the `kimi web` local server, valid alongside the bearer token; recommended when binding the server beyond loopback — see [Local server and API](../guides/server.md#authentication) | Any non-empty string; when unset, only the token is valid |
| `KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT` | Whether to keep background tasks when the session closes; takes higher priority than `config.toml`. The default is to stop them on exit | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
| `KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS` | Cap on concurrently running background tasks; takes higher priority than `[background] max_running_tasks` in `config.toml` (unset means no cap) | Positive integer; invalid values are ignored |
| `KIMI_IMAGE_MAX_EDGE_PX` | Longest-edge ceiling (px) for image compression; takes higher priority than `[image] max_edge_px` in `config.toml` (default `2000`) | Positive integer; invalid values are ignored |

116
docs/en/guides/server.md Normal file
View file

@ -0,0 +1,116 @@
# Local Server and API
Kimi Code CLI ships with a built-in local server: running `kimi web` starts a foreground process that mounts three things at once — the web UI in your browser, a REST API (`/api/v1`), and a WebSocket event stream (`/api/v1/ws`). The web UI lets you use Kimi Code in a browser; the REST and WebSocket APIs are for scripts and third-party tools, letting you create sessions, submit prompts, and follow execution from code — all reading and writing the same session data as the TUI and the web UI.
> Make sure Kimi Code CLI is installed and ready to use first — either logged in via `/login` (in the TUI, or `kimi login`), or with a provider configured in `config.toml`. The server shares the CLI's login state and configuration, so no separate credential is needed for it.
::: 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.
:::
## Start the server
```sh
kimi web # run the server in the foreground and open the browser
kimi web --no-open # run the server only, don't open the browser
kimi web --port 58628 # pick a specific bind port
```
The server binds to `127.0.0.1:58627` by default (loopback only). If the port is taken it automatically retries with the next one, so multiple instances can coexist on the same machine; each instance registers under `~/.kimi-code/server/instances/`. The startup banner prints the access URL and the plaintext token:
```text
Local: http://127.0.0.1:58627/#token=...
Token: ...
Stop: Ctrl+C
```
The server runs in the foreground; press `Ctrl-C` for a clean shutdown. For the full option list such as `--host` and `--log-level`, see the [kimi command reference](../reference/kimi-command.md#kimi-web).
## Authentication
Every `/api/*` endpoint requires a bearer token (any request carrying this string is treated as authorized). The token is generated on the first server boot, persisted at `~/.kimi-code/server.token` (file mode 0600), and reused across restarts.
Pick the carrying method that fits your client:
- **REST**: the `Authorization: Bearer <token>` request header.
- **web UI**: the URL in the startup banner carries a `#token=` fragment, so opening it in a browser completes sign-in automatically. The fragment is never sent to the server.
- **WebSocket**: clients that can set headers use `Authorization: Bearer`; clients that cannot (such as browsers) pass the subprotocol (a protocol name declared during the WebSocket handshake) `kimi-code.bearer.<token>` instead.
If the token leaks, run `kimi web rotate-token`: the new token is written to `server.token` immediately, the old one stops working at once, and running instances pick up the new token without a restart.
If you bind the server to a non-loopback address (`--host`), also set the `KIMI_CODE_PASSWORD` environment variable as a parallel credential; the server then rate-limits authentication failures automatically.
::: danger
`--dangerous-bypass-auth` disables authentication entirely — anyone who can reach the port can control your sessions, file system, and shell. Only use it on trusted networks or behind your own authenticating proxy. See the [kimi command reference](../reference/kimi-command.md#kimi-web).
:::
## Drive a session over the API
The minimal flow with curl: check the server → create a session → subscribe to events → submit a prompt → read history back. The examples assume the server runs at the default address and the token is stored in the shell variable `TOKEN`.
1. Check server status:
```sh
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:58627/api/v1/meta
```
Every JSON response is wrapped in a uniform envelope — `{ "code": 0, "msg": "success", "data": ..., "request_id": "..." }`. The business outcome lives in `code` (`0` means success); the HTTP status only reports transport-level results.
2. Create a session; `metadata.cwd` sets the working directory:
```sh
curl -s -X POST http://127.0.0.1:58627/api/v1/sessions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"metadata": {"cwd": "/path/to/project"}}'
```
The returned `data.id` (shaped like `session_...`) is the session id used by every subsequent request.
3. Connect to the WebSocket and subscribe to session events. Any WebSocket client works; below is a dependency-free Node.js script (Node.js 22+ ships a built-in `WebSocket` client):
```js
// subscribe.mjs — usage: TOKEN=... node subscribe.mjs session_...
const ws = new WebSocket('ws://127.0.0.1:58627/api/v1/ws', [
`kimi-code.bearer.${process.env.TOKEN}`,
]);
ws.onmessage = (e) => console.log(e.data);
ws.onopen = () =>
ws.send(
JSON.stringify({
type: 'subscribe',
id: '1',
payload: { session_ids: [process.argv[2]] },
}),
);
```
4. Submit a prompt:
```sh
curl -s -X POST http://127.0.0.1:58627/api/v1/sessions/<session_id>/prompts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": [{"type": "text", "text": "Introduce this repository in one sentence"}]}'
```
The subscriber sees, in order: `turn.started` (turn begins) → `assistant.delta` (streaming text increments) → `tool.call.started` / `tool.result` when tool calls happen → `turn.ended` (turn finishes).
5. Read history back over REST at any time:
```sh
curl -s -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:58627/api/v1/sessions/<session_id>/messages?page_size=20"
```
## Live specification documents
While running, the server describes itself with two specification documents, both requiring the bearer token:
- `GET /openapi.json` — an OpenAPI document for the REST API, with request/response schemas for every endpoint; import it into Swagger UI, Postman, and similar tools.
- `GET /asyncapi.json` — an AsyncAPI document for the WebSocket protocol, covering control frames and event types.
## Next steps
- [Server API](../reference/server-api.md) — full REST endpoint inventory, error codes, WebSocket events, and the transcript protocol
- [kimi command](../reference/kimi-command.md#kimi-web) — all `kimi web` command-line options

View file

@ -157,7 +157,7 @@ kimi acp
Run the local Kimi server in the foreground of the current terminal — a single process that exposes the REST + WebSocket API and serves the web UI from the same origin — and open the web UI in the default browser once it is ready. The command stays attached to the terminal and shuts down cleanly on `SIGINT` / `SIGTERM` (e.g. `Ctrl-C`).
When the server is running, `GET /openapi.json` returns the REST OpenAPI document and `GET /asyncapi.json` returns the local WebSocket AsyncAPI document.
When the server is running, `GET /openapi.json` returns the REST OpenAPI document and `GET /asyncapi.json` returns the local WebSocket AsyncAPI document. For an end-to-end walkthrough of driving sessions over the API, see [Local server and API](../guides/server.md); for the protocol details, see the [Server API](./server-api.md) reference.
```sh
kimi web # run the server in the foreground and open the browser

View file

@ -0,0 +1,344 @@
# 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](./kimi-command.md#kimi-web) reference; for an end-to-end walkthrough, see [Local server and API](../guides/server.md).
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:
- `OPTIONS` preflight requests
- `GET /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](../guides/server.md#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:
```json
{
"code": 0,
"msg": "success",
"data": {},
"request_id": "01JZX4A6E7M8V0R3Q0N2K2M5Q9"
}
```
- `code`: the business outcome; `0` means success. See the error-code bands below.
- `data`: the payload on success. Note that some "error" envelopes also carry a non-null `data` — for example, resolving an already-resolved approval returns `40902` with `data.resolved` set to `false` — so clients should check `code` first, then `data`.
- `request_id`: a ULID for this request. Clients may supply one via the `X-Request-Id` header; 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](#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) plus `page_size` (1100), 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 by `POST /api/v1/search` and `GET /api/v2/sessions`. Changing any query condition mid-pagination invalidates the token: v2 returns `40922`, search returns `40001`.
## 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` | 1100, 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](#authentication) above). Once connected, the server immediately sends `server_hello`:
```json
{
"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](#response-envelope) — clients must keep checking the envelope `code` on those endpoints.
## Next steps
- [Local server and API](../guides/server.md) — startup, authentication, and the end-to-end calling flow
- [kimi command](./kimi-command.md#kimi-web) — all `kimi web` command-line options

View file

@ -121,6 +121,7 @@ kimi
| 环境变量 | 用途 | 合法值 |
| --- | --- | --- |
| `KIMI_DISABLE_TELEMETRY` | 关闭匿名遥测上报 | `1``true``yes``y`(不区分大小写) |
| `KIMI_CODE_PASSWORD` | 为 `kimi web` 本地服务设置并列鉴权密码,与 bearer token 同时有效;把服务绑定到非本机地址时建议设置,见[本地服务与 API](../guides/server.md#鉴权) | 任意非空字符串;未设置时仅 token 有效 |
| `KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT` | 会话关闭时是否保留后台任务,优先级高于 `config.toml`。默认会在退出时停止后台任务 | 真值:`1`/`true`/`yes`/`on`;假值:`0`/`false`/`no`/`off` |
| `KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS` | 同时运行的后台任务数上限,优先级高于 `config.toml``[background] max_running_tasks`(不设置表示无上限) | 正整数;非法值被忽略 |
| `KIMI_IMAGE_MAX_EDGE_PX` | 图片压缩的最长边上限(像素),优先级高于 `config.toml``[image] max_edge_px`(默认 `2000` | 正整数;非法值被忽略 |

116
docs/zh/guides/server.md Normal file
View file

@ -0,0 +1,116 @@
# 本地服务与 API
Kimi Code CLI 内置一个本地服务:运行 `kimi web` 会在前台启动一个进程,同时挂载浏览器里的 web UI、REST API`/api/v1`)和 WebSocket 事件流(`/api/v1/ws`。web UI 用于在浏览器里直接使用 Kimi CodeREST 与 WebSocket API 面向脚本和第三方工具,可以用代码创建会话、提交提示词、实时跟进执行过程——它们与 TUI、web UI 读写同一份会话数据。
> 开始前请确认 Kimi Code CLI 已安装并处于可用状态——完成 `/login` 登录TUI 内或 `kimi login`),或已在 `config.toml` 配置供应商。服务与 CLI 共享同一份登录态与配置,无需为服务单独准备凭证。
::: warning 注意
本页介绍的 REST 与 WebSocket API 为实验性特性:不保证接口稳定性,端点、字段与事件类型可能随版本随时更改。集成时请以当前版本服务的 `/openapi.json``/asyncapi.json` 为准。
:::
## 启动服务
```sh
kimi web # 前台运行服务并打开浏览器
kimi web --no-open # 只运行服务,不打开浏览器
kimi web --port 58628 # 指定绑定端口
```
服务默认绑定 `127.0.0.1:58627`(仅本机访问);端口被占用时自动 +1 重试,同一台机器因此可以并存多个实例,每个实例登记在 `~/.kimi-code/server/instances/` 下。启动横幅会打印访问地址和明文 token
```text
Local: http://127.0.0.1:58627/#token=...
Token: ...
Stop: Ctrl+C
```
服务在前台运行,按 `Ctrl-C` 干净退出。`--host``--log-level` 等完整选项见 [kimi 命令参考](../reference/kimi-command.md#kimi-web)。
## 鉴权
所有 `/api/*` 接口都要求 bearer token持有者令牌任何携带该字符串的请求都被视为已授权。token 在首次启动服务时生成,持久化在 `~/.kimi-code/server.token`(文件权限 0600跨重启复用。
按客户端类型选择携带方式:
- **REST**:请求头 `Authorization: Bearer <token>`
- **web UI**:启动横幅里的地址自带 `#token=` 片段,浏览器打开后自动完成登录;该片段不会发送到服务端。
- **WebSocket**:能自定义请求头的客户端用 `Authorization: Bearer`浏览器等不能自定义头的客户端改用子协议WebSocket 握手时声明的协议名)`kimi-code.bearer.<token>`
token 泄露时运行 `kimi web rotate-token` 轮换:新 token 立即写入 `server.token`,旧 token 即刻失效,正在运行的实例无需重启。
如果把服务绑定到非本机地址(`--host`),建议额外设置 `KIMI_CODE_PASSWORD` 环境变量作为并列凭证;此时服务端会对鉴权失败自动限流。
::: danger 警告
`--dangerous-bypass-auth` 会彻底关闭鉴权,任何能访问该端口的人都能控制你的会话、文件系统和 shell。仅在可信网络或自有鉴权代理之后使用详见 [kimi 命令参考](../reference/kimi-command.md#kimi-web)。
:::
## 用 API 驱动一个会话
下面用 curl 走一遍最小流程:确认服务状态 → 创建会话 → 订阅事件 → 提交提示词 → 回读历史。示例假设服务跑在默认地址token 已存入 shell 变量 `TOKEN`
1. 确认服务状态:
```sh
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:58627/api/v1/meta
```
所有 JSON 响应都包在统一信封里——`{ "code": 0, "msg": "success", "data": ..., "request_id": "..." }`,业务结果以 `code` 为准(`0` 表示成功HTTP 状态码只表达传输层结果。
2. 创建会话,`metadata.cwd` 指定工作目录:
```sh
curl -s -X POST http://127.0.0.1:58627/api/v1/sessions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"metadata": {"cwd": "/path/to/project"}}'
```
返回的 `data.id`(形如 `session_...`)就是后续所有请求要用的会话 id。
3. 连接 WebSocket 并订阅会话事件。任何 WebSocket 客户端都可以;下面是一个零依赖的 Node.js 脚本Node.js 22+ 内置 `WebSocket` 客户端):
```js
// subscribe.mjs —— 用法TOKEN=... node subscribe.mjs session_...
const ws = new WebSocket('ws://127.0.0.1:58627/api/v1/ws', [
`kimi-code.bearer.${process.env.TOKEN}`,
]);
ws.onmessage = (e) => console.log(e.data);
ws.onopen = () =>
ws.send(
JSON.stringify({
type: 'subscribe',
id: '1',
payload: { session_ids: [process.argv[2]] },
}),
);
```
4. 提交提示词:
```sh
curl -s -X POST http://127.0.0.1:58627/api/v1/sessions/<session_id>/prompts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": [{"type": "text", "text": "用一句话介绍这个仓库"}]}'
```
订阅端会依次看到 `turn.started`(轮次开始)→ `assistant.delta`(流式文本增量)→ 发生工具调用时的 `tool.call.started` / `tool.result``turn.ended`(轮次结束)。
5. 随时可以用 REST 回读历史消息:
```sh
curl -s -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:58627/api/v1/sessions/<session_id>/messages?page_size=20"
```
## 在线规范文档
服务运行时会自描述两份规范文档,同样需要 bearer token
- `GET /openapi.json` — REST API 的 OpenAPI 文档,含每个端点的请求 / 响应 schema可直接导入 Swagger UI、Postman 等工具。
- `GET /asyncapi.json` — WebSocket 协议的 AsyncAPI 文档,覆盖控制帧与事件类型。
## 下一步
- [服务 API](../reference/server-api.md) — REST 端点全集、错误码、WebSocket 事件与转录协议
- [kimi 命令](../reference/kimi-command.md#kimi-web) — `kimi web` 的全部命令行选项

View file

@ -157,7 +157,7 @@ kimi acp
在当前终端前台运行本地 Kimi 服务 —— 同一个进程同时挂载 REST + WebSocket API 与 web UI —— 并在服务就绪后用默认浏览器打开 web UI。命令会一直挂在终端直到收到 `SIGINT` / `SIGTERM`(如 `Ctrl-C`)时干净退出。
服务运行时,`GET /openapi.json` 会返回 REST OpenAPI 文档,`GET /asyncapi.json` 会返回本地 WebSocket 协议的 AsyncAPI 文档。
服务运行时,`GET /openapi.json` 会返回 REST OpenAPI 文档,`GET /asyncapi.json` 会返回本地 WebSocket 协议的 AsyncAPI 文档。用 API 驱动会话的完整流程见[本地服务与 API](../guides/server.md),协议细节见[服务 API](./server-api.md)。
```sh
kimi web # 前台运行服务并打开浏览器

View file

@ -0,0 +1,344 @@
# 服务 API
`kimi web` 启动的本地服务暴露两组程序化接口REST API`/api/v1`,另有 `/api/v2/sessions`)和 WebSocket 事件流(`/api/v1/ws`)。本页是这两组接口的协议参考;服务的启动方式与命令行选项见 [kimi 命令](./kimi-command.md#kimi-web),端到端的上手流程见[本地服务与 API](../guides/server.md)。
每个端点的完整请求 / 响应 schema 以服务自描述的规范文档为准:`GET /openapi.json`OpenAPI`GET /asyncapi.json`AsyncAPI两者都需要鉴权。
::: warning 注意
本页描述的 REST 与 WebSocket API 为实验性特性:不保证接口稳定性,端点、字段与事件类型可能随版本随时更改。集成时请以当前版本服务的 `/openapi.json``/asyncapi.json` 为准。
:::
## 基础约定
### 地址
默认地址 `http://127.0.0.1:58627`;端口被占用时自动 +1 重试(至多 100 次),可用 `--port` / `--host` 修改。同一 home 目录可并存多个实例,运行中的实例登记在 `~/.kimi-code/server/instances/`
### 鉴权
除以下例外,所有 `/api/*` 路径(含 `/openapi.json``/asyncapi.json`)都要求 bearer token
- `OPTIONS` 预检请求
- `GET /api/v1/healthz`(探活)
- 静态 web 资源(非 `/api/` 路径)
携带方式REST 用 `Authorization: Bearer <token>` 请求头WebSocket 升级请求可用同一请求头,或子协议 `kimi-code.bearer.<token>`。token 的生成与轮换见[本地服务与 API鉴权](../guides/server.md#鉴权)。
鉴权失败返回 HTTP 401信封 `code``40101`。在非 loopback 绑定上,同一来源 60 秒内鉴权失败 10 次会被封禁 60 秒,期间一律返回 HTTP 429`code``42901`)。
### 响应信封
所有 JSON 响应统一包在信封里:
```json
{
"code": 0,
"msg": "success",
"data": {},
"request_id": "01JZX4A6E7M8V0R3Q0N2K2M5Q9"
}
```
- `code`:业务结果,`0` 表示成功;错误码分段见下文。
- `data`:成功时的业务数据。注意部分「错误」信封也携带非空 `data`——例如重复解决审批返回 `40902``data.resolved``false`——客户端应先判 `code` 再看 `data`
- `request_id`:本次请求的 ULID客户端可用 `X-Request-Id` 请求头指定,非法值会被服务端重新生成。
HTTP 状态码几乎总是 200业务结果以 `code` 为准。例外情况:
| 场景 | HTTP 状态 |
| --- | --- |
| 鉴权失败 / 触发限流 | 401 / 429 |
| 创建供应商、导入供应商目录成功 | 201 |
| 删除供应商成功 | 204 |
| 二进制与流式端点 | 支持时返回 206Range 分段)/ 304ETag 未变),各端点能力不同,详见「[二进制与流式端点](#二进制与流式端点)」 |
| `GET /api/v1/files/{file_id}` 下载错误 | 真实 404 / 500响应体仍为信封 |
其中 201 的响应体仍是标准信封(`code``0`),只是状态行遵循 REST 的资源创建习惯204 按 HTTP 语义没有响应体,删除成功以状态码本身为准。
### 错误码
错误码按段位分组:
| 段位 | 含义 | 示例 |
| --- | --- | --- |
| `0` | 成功 | |
| `400xx` | 请求参数错误 | `40001` 校验失败(`details` 逐字段说明)、`40003` 供应商由 OAuth 托管 |
| `401xx` | 鉴权与就绪状态 | `40101` 未授权、`40110` 未配置供应商、`40113` 模型未解析 |
| `404xx` | 资源不存在 | `40401` 会话、`40408` MCP 服务、`40409` 文件路径 |
| `409xx` | 状态冲突 | `40901` 会话忙、`40902` 审批已解决、`40922` 分页条件与 `page_token` 不符 |
| `410xx` | 资源已过期 | `41001` 审批超时、`41002` 提问超时、`41003` 临时文件过期 |
| `413xx` | 体积或边界超限 | `41302` 读取文件超 10 MB、`41304` 路径越出会话目录 |
| `429xx` | 限流 | `42901` 鉴权失败封禁、`42902` 文件监听数超限 |
| `500xx` | 服务端内部错误 | `50001` 未捕获异常、`50003` 持久化失败 |
| `6xxxx` / `7xxxx` / `8xxxx` | 工具运行时 / LLM 供应商 / MCP 透传错误,`msg` 保留上游原文 | |
### 分页
列表端点有两种分页风格:
- **游标式**`before_id` / `after_id`(互斥)加 `page_size`1100响应为 `{ items, has_more }`。用于会话列表、消息列表、转录等。
- **`page_token`**:不透明令牌(内部绑定了查询条件指纹),用于 `POST /api/v1/search``GET /api/v2/sessions`。翻页途中改变任何查询条件会使令牌失效v2 返回 `40922`search 返回 `40001`
## REST 端点
按资源分组列出端点。路径里的 `:{action}` 是动作后缀约定——对单个资源 POST 到 `路径:动作` 执行非 CRUD 操作(如会话的 `:fork``:archive`)。
### 服务与元信息
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/healthz` | 探活,免鉴权 |
| `GET /api/v1/meta` | 服务版本、能力集、`server_id`、实验开关等 |
| `POST /api/v1/shutdown` | 优雅退出(先回 200 再关闭);仅 loopback 绑定时挂载 |
### 登录与用量
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/auth` | 登录就绪状态快照 |
| `POST /api/v1/oauth/login` | 发起 OAuth device-code 登录流程 |
| `GET /api/v1/oauth/login` | 轮询登录流程状态 |
| `DELETE /api/v1/oauth/login` | 取消进行中的登录流程 |
| `POST /api/v1/oauth/logout` | 登出托管供应商 |
| `GET /api/v1/oauth/usage` | 查询套餐用量与限额 |
| `GET /api/v1/oauth/userinfo` | 查询账号资料 |
### 配置
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/config` | 读取全局配置(密钥字段脱敏) |
| `POST /api/v1/config` | 合并式更新配置,并广播 `event.config.changed` |
### 模型与供应商
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/models` | 列出已配置的模型别名 |
| `POST /api/v1/models/{model_id}:set_default` | 设置全局默认模型 |
| `GET /api/v1/providers` | 列出供应商 |
| `POST /api/v1/providers` | 创建供应商201 |
| `GET /api/v1/providers/{provider_id}` | 读取供应商(含已存密钥) |
| `PUT /api/v1/providers/{provider_id}` | 整体替换供应商配置 |
| `DELETE /api/v1/providers/{provider_id}` | 删除供应商204 |
| `POST /api/v1/providers/{provider_id}:refresh` | 刷新该供应商的模型元数据 |
| `POST /api/v1/providers:{action}` | 集合级动作:`refresh` / `refresh_oauth` / `import_catalog` / `import_registry` |
| `GET /api/v1/catalog/providers` | 浏览 models.dev 目录(服务端代理) |
| `GET /api/v1/catalog/providers/{catalog_id}` | 读取目录中单个条目 |
### 会话
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/sessions` | 创建会话(需 `workspace_id``metadata.cwd` |
| `GET /api/v1/sessions` | 列出会话,游标分页,支持 `busy` / `archived_only` 等过滤 |
| `GET /api/v1/sessions/{session_id}` | 读取单个会话 |
| `GET /api/v1/sessions/{session_id}/profile` | 读取会话档案 |
| `POST /api/v1/sessions/{session_id}/profile` | 更新标题、元数据、agent 配置 |
| `POST /api/v1/sessions/{session_id}:{action}` | 会话动作:`fork` / `compact` / `undo` / `abort` / `btw` / `archive` / `restore` |
| `GET /api/v1/sessions/{session_id}/children` | 列出子会话 |
| `POST /api/v1/sessions/{session_id}/children` | 创建子会话fork 并打标) |
| `GET /api/v1/sessions/{session_id}/status` | 实时状态汇总 |
| `GET /api/v1/sessions/{session_id}/goal` | 当前目标快照(无则 `null` |
| `GET /api/v1/sessions/{session_id}/warnings` | 会话级告警 |
| `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息zip 流,不走信封) |
| `GET /api/v1/sessions/{session_id}/snapshot` | 客户端重建用全量快照(含 `as_of_seq``epoch` |
### 消息与转录
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/messages` | 消息分页(`before_id` / `after_id` / `role` |
| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | 读取单条消息 |
| `GET /api/v1/sessions/{session_id}/transcript` | 转录按轮次分页(需 `agent_id`),全局状态不分页随响应返回 |
| `GET /api/v1/sessions/{session_id}/transcript/ops` | 转录批次补漏(`since_seq``complete: false` 时需全量刷新 |
| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | 各轮次的用户输入,不分页 |
| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode 计划内容、路径与审阅结果 |
### 提示词
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/prompts` | 进行中与排队中的提示词 |
| `POST /api/v1/sessions/{session_id}/prompts` | 提交提示词(内容块数组,可带模型 / 权限模式等覆盖) |
| `POST /api/v1/sessions/{session_id}/prompts:steer` | 把排队的提示词插入当前轮次 |
| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | 中止进行中的提示词 |
| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | 插入单个排队提示词 |
### 审批与提问
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/approvals` | 列出审批请求(可按 `status=pending` 过滤) |
| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | 答复审批 |
| `GET /api/v1/sessions/{session_id}/questions` | 列出提问 |
| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | 回答提问 |
| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | 忽略提问 |
### 后台任务
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/tasks` | 列出后台任务 |
| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | 读取任务(可选输出预览) |
| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | 取消任务 |
### 技能、工具与 MCP
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/skills` | 会话级技能目录 |
| `GET /api/v1/workspaces/{workspace_id}/skills` | 无会话的工作区技能目录 |
| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | 激活技能(开启一个轮次) |
| `GET /api/v1/tools` | 列出当前生效 agent 的工具 |
| `GET /api/v1/mcp/servers` | 列出 MCP 服务 |
| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | 重启 MCP 服务 |
### 终端
PTY 终端接口,仅 loopback 绑定时挂载。
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/terminals` | 列出终端 |
| `POST /api/v1/sessions/{session_id}/terminals` | 创建终端 |
| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | 读取终端(含回滚缓冲) |
| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | 关闭终端 |
### 工作区
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/workspaces` | 列出已注册工作区 |
| `POST /api/v1/workspaces` | 注册工作区(按根路径幂等) |
| `PATCH /api/v1/workspaces/{workspace_id}` | 重命名 |
| `DELETE /api/v1/workspaces/{workspace_id}` | 注销(保留磁盘内容) |
| `GET /api/v1/workspaces/{workspace_id}/trust` | 读取信任状态 |
| `POST /api/v1/workspaces/{workspace_id}/trust` | 授予信任 |
| `POST /api/v1/workspaces/{workspace_id}/untrust` | 撤销信任 |
### 文件系统
会话内文件操作为 `POST /api/v1/sessions/{session_id}/fs:{action}`,动作包括 `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`,请求体为 JSON。另有
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/workspace/fs:search` | 无会话的工作区搜索body 携带工作区引用) |
| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话文件(二进制,见下文) |
| `GET /api/v1/fs:browse` | 列出本机目录(文件夹选择器用) |
| `GET /api/v1/fs:home` | 用户主目录与最近工作区 |
| `GET /api/v1/fs:content` | 读取本机任意文件原始字节(仅受 token 保护,谨慎暴露端口) |
| `POST /api/v1/fs:mkdir` | 按绝对路径创建目录 |
### 文件上传
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/files` | multipart 上传(字段 `file`,可选 `name``expires_in_sec`),返回文件元信息 |
| `GET /api/v1/files/{file_id}` | 下载(二进制,错误用真实 HTTP 状态码) |
| `DELETE /api/v1/files/{file_id}` | 删除 |
### 全局搜索与其他
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/search` | 跨会话全文搜索,`mode``terms`(默认)或 `literal`(精确子串),`page_token` 分页 |
| `GET /api/v1/connections` | 列出当前在线的 WebSocket 连接 |
| `GET /api/v2/sessions` | 新一代会话列表,见下节 |
| `/api/v1/debug/*` | 反射式调试 RPC`--debug-endpoints` 且 loopback 时挂载,不属于稳定协议 |
### `GET /api/v2/sessions`
面向列表页的新一代会话查询,筛选、排序、字段组都在查询参数里:
| 参数 | 说明 |
| --- | --- |
| `workspace.id` | 按工作区过滤,可重复 |
| `activity.status` | 按活动状态过滤:`running` / `approval` / `question` / `failed` / `idle`,可重复 |
| `meta.updated_after` | 只看该时间epoch 毫秒)之后更新过的会话 |
| `meta.archived` | `true` / `false`(默认)/ `all` |
| `sort` | `meta.updated_at_desc`(默认)/ `meta.updated_at_asc` / `meta.created_at_desc` |
| `include` | 逗号分隔的附加字段组;目前支持 `git`(分支与 PR 信息,按目录去重并缓存 60 秒) |
| `page_size` | 1100默认 50 |
| `page_token` | 上一页返回的翻页令牌 |
响应每项固定包含 `workspace``meta``activity` 三组,`include=git` 时附加 `git` 组。翻页令牌绑定首页查询条件,中途改条件返回 `40922`
## WebSocket 协议
### 建立连接
唯一端点是 `ws://<host>:<port>/api/v1/ws`,升级请求即完成鉴权(方式见上文「鉴权」)。连接建立后服务端立即发送 `server_hello`
```json
{
"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 }
}
}
```
注意服务端不发送心跳,也不会主动断开空闲连接——保活与重连由客户端自己负责。
### 控制帧
客户端发送 JSON 帧 `{ "type", "id"?, "payload" }`;每个请求帧都会收到应答 `{ "type": "ack", "id", "code", "msg", "payload" }``code``0` 表示成功。
| 帧 | payload | 说明 |
| --- | --- | --- |
| `subscribe` | `{ session_ids, cursors?, agent_filter? }` | 订阅会话事件;带 `cursors`(每会话 `{seq, epoch}`)时回放错过的持久事件 |
| `unsubscribe` | `{ session_ids }` | 取消会话订阅 |
| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | 订阅转录流(唯一的转录订阅通道),`transcript` 按 agent 指定粒度 |
| `unsubscribe_v2` | `{ session_id, agent_ids? }` | 退订转录流;省略 `agent_ids` 表示整个会话 |
| `watch_fs_add` / `watch_fs_remove` | `{ session_id, paths, recursive? }` | 订阅 / 取消文件变更通知(`event.fs.changed` |
| `client_hello` | `{ client_id }` | 握手帧,其余字段为遗留兼容 |
### 事件
事件帧形状为 `{ "type", "seq", "epoch"?, "volatile"?, "offset"?, "session_id"?, "timestamp", "payload" }``type` 即事件类型。按投递范围分两类:
- **全局事件**:发送到每个已建立连接,无需订阅——`session.meta.updated``event.session.created``event.session.work_changed``event.session.status_changed``event.workspace.*``event.config.*`
- **会话事件**:只发给订阅了该会话的连接,受 `agent_filter` 过滤。主要事件族:
| 事件族 | 主要事件 |
| --- | --- |
| 轮次 | `turn.started``turn.ended``turn.step.started` / `completed` / `interrupted` / `retrying` |
| 流式文本 | `assistant.delta``thinking.delta`(带 `offset` 用于对齐) |
| 工具调用 | `tool.call.started``tool.call.delta``tool.progress``tool.result` |
| 交互 | `event.approval.requested` / `resolved``event.question.requested` / `answered` / `dismissed` |
| 子 Agent | `subagent.spawned` / `started` / `suspended` / `completed` / `failed` |
| 后台 | `task.started` / `terminated``shell.started` / `output` / `completed` |
| 其他 | `compaction.*``skill.activated``goal.updated``prompt.*``error``warning` |
事件另分持久与易失两种:持久事件带严格递增的 `seq`,落盘并可回放;易失事件(各 `*.delta``tool.progress``shell.*` 等)标 `volatile: true`,不回放。消费易失文本流时用 `offset`(该轮次内的累计字符偏移)与本地已累积文本比对:小于本地长度说明是重复帧,大于说明有缺漏、需走快照恢复。
### 断线恢复
重连后在 `subscribe``cursors` 里带上每个会话最后应用事件的 `{seq, epoch}`服务端会回放缺口落后超过缓冲1000 条)或游标失效时改为收到 `resync_required`。此时调用 `GET /api/v1/sessions/{session_id}/snapshot` 拿全量快照(含 `as_of_seq``epoch`),再以新游标重新订阅。
### 转录协议
`subscribe_v2``transcript` 按 agent 指定粒度:`off` / `turn` / `block` / `delta`(键 `"*"` 表示默认粒度),粒度越高推送越细。粒度非 `off` 的 agent 走两帧推送:`transcript.reset`(基线快照,历史经 REST 分页回读)和 `transcript.ops`(增量批次,带每个 agent 连续递增的 `seq`);该 agent 的旧式事件在同一连接上被抑制,改由转录帧承载。断线时用 `transcript_since` 续传服务端批次日志无法覆盖缺口时REST 补漏返回 `complete: false`需全量刷新。REST 侧对应 `GET .../transcript`(按轮次分页)与 `GET .../transcript/ops?since_seq=`(批次补漏)。
## 二进制与流式端点
以下端点返回二进制流而非 JSON 载荷,各端点的 HTTP 能力并不相同:
| 方法与路径 | 说明 | Range 分段206 | ETag / 304 |
| --- | --- | --- | --- |
| `GET /api/v1/files/{file_id}` | 下载已上传文件 | 支持 | 不支持(会发送 `etag` 头,但不处理 `If-None-Match` |
| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话工作区文件 | 支持 | 支持 |
| `GET /api/v1/fs:content` | 读取本机任意文件(仅受 token 保护,谨慎暴露端口) | 支持 | 支持 |
| `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息zip 流) | 不支持 | 不支持 |
错误语义也不相同:`GET /api/v1/files/{file_id}` 对查找和存储失败返回真实 404 / 500 状态码(参数校验失败仍走 HTTP 200 信封),其余三个端点的所有失败都走标准[响应信封](#响应信封)——客户端在这三个端点上仍需检查信封中的 `code`
## 下一步
- [本地服务与 API](../guides/server.md) — 启动、鉴权与端到端调用流程
- [kimi 命令](./kimi-command.md#kimi-web) — `kimi web` 的全部命令行选项