Server API
The local server started by kimi web exposes two programmatic surfaces: a REST API (/api/v1, plus /api/v2/sessions and /api/v2/mcp) 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 Drive a session over the API below.
This page is a curated, human-readable reference: it documents every endpoint's parameters, request bodies, and response shapes below. The precise machine-readable schema of every endpoint is owned by the server's live specification documents: GET /openapi.json (OpenAPI) and GET /asyncapi.json (AsyncAPI), both generated from the same validation schemas the server enforces at runtime. Both require authentication; when this page and the live spec ever disagree, the live spec wins.
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 Using Kimi Code in the browser: Getting started.
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.GET /api/v2/sessionsalso offers a statelesspagepage-number mode as an alternative.
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.
- Check server status:
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:58627/api/v1/metaEvery 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.
- Create a session;
metadata.cwdsets the working directory:
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.
- 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
WebSocketclient):
// 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]] },
}),
);- Submit a prompt:
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).
- Read history back over REST at any time:
curl -s -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:58627/api/v1/sessions/<session_id>/messages?page_size=20"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 |
GET /api/v1/healthz
Liveness probe for scripts and process supervisors. It is the one /api endpoint exempt from the bearer token (see Authentication) and answers without touching config or the engine.
On success, data is { "ok": true }.
GET /api/v1/meta
Returns this instance's identity and capability map. Most fields are frozen at boot; experimental_flags and features are resolved per request, so a flag flip or a failed feature shows up in the next response.
On success, data carries:
| Field | Type | Description |
|---|---|---|
server_version | string | Server version |
capabilities | object | Capability map — websocket, file_upload, fs_query, mcp, tasks, terminal, all always true |
server_id | string | Unique id of this server instance |
started_at | string | Boot time, ISO 8601 |
open_in_apps | array | Host apps usable as open-in targets (finder / cursor / vscode / iterm / terminal); currently always empty |
dangerous_bypass_auth | boolean | Whether the server was started with --dangerous-bypass-auth (clients may skip the token prompt) |
backend | string | Engine backend, v1 or v2; always v2 for this server |
web_title | string | Custom browser tab title from --web-title; omitted when unset |
experimental_flags | object | Experimental flag id → enabled, resolved at request time |
features | array | Engine features as { name, state, meta }; state is Pending / Activating / Active / Unloading / Failed |
POST /api/v1/shutdown
Asks the server to shut down gracefully. The reply is sent first and the shutdown runs immediately after, so the caller can trust the response it received. The route is mounted only on loopback binds — on a non-loopback bind it is not registered at all (requests hit a 404) unless the server was started with --allow-remote-shutdown.
On success, data is { "ok": true }.
Login and usage
These endpoints drive the managed Kimi OAuth login lifecycle and expose account-level information. The managed provider is named managed:kimi-code; the optional provider parameter on every endpoint below defaults to it.
| Method and path | Description |
|---|---|
GET /api/v1/auth | Auth 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 quota and booster wallet |
GET /api/v1/oauth/userinfo | Account profile |
GET /api/v1/oauth/region | Resolve the client region (mainland-cn / global) |
GET /api/v1/auth
Auth snapshot: whether the default model resolves to a usable provider configuration, plus the managed provider's login state. models_ready is true when the global default_model alias exists in the model table and resolves to a configured provider — including providerless flat models carrying their own base_url and models injected through KIMI_MODEL_* environment variables. It does not verify credentials, so a prompt can still fail afterwards with 40111 / 40112.
On success, data carries models_ready (boolean), providers_count (number of configured providers), and managed_provider (null, or { name, status } with status one of authenticated / expired / revoked / unauthenticated). The global default model alias itself is read from GET /api/v1/config (default_model), not from this endpoint.
POST /api/v1/oauth/login
Starts an OAuth device-code login flow for the managed provider; starting a new flow aborts any pending flow for the same provider. When the account is already authenticated, no user interaction is needed and the response reports authenticated immediately.
| Parameter | In | Type | Description |
|---|---|---|---|
provider | body | string | Managed provider name. Default managed:kimi-code |
region | body | string | mainland-cn or global; overrides the region resolution described under GET /api/v1/oauth/region for this flow |
On success, data has one of two shapes. A pending flow — { flow_id, provider, status: "pending", verification_uri, verification_uri_complete, user_code, expires_in, interval, expires_at }: open verification_uri_complete (or verification_uri and enter user_code), then poll GET /api/v1/oauth/login every interval seconds until the flow resolves or expires_at passes (expires_in is the same deadline in seconds). The already-authenticated fast path — { flow_id, provider, status: "authenticated" }.
GET /api/v1/oauth/login
Polls the login flow state for a provider. Returns null when no flow has been started.
| Parameter | In | Type | Description |
|---|---|---|---|
provider | query | string | Managed provider name. Default managed:kimi-code |
On success, data is null or a flow snapshot: { flow_id, provider, status, verification_uri, verification_uri_complete, user_code, expires_in, expires_at, interval }, where status is pending / authenticated / denied / expired / cancelled. Once the flow leaves pending, resolved_at records when it reached its terminal state and error_message describes a failed flow.
DELETE /api/v1/oauth/login
Cancels the pending login flow for a provider. When no flow is pending, the call is a no-op that reports the last known state.
| Parameter | In | Type | Description |
|---|---|---|---|
provider | query | string | Managed provider name. Default managed:kimi-code |
On success, data is { cancelled, status }: cancelled is true only when a pending flow was actually aborted, and status is the flow state after the call.
POST /api/v1/oauth/logout
Logs out the managed provider: discards the stored OAuth credential, aborts any pending login flow, and removes the managed provider from the configuration. OAuth-managed providers reject manual edit and delete (see PUT / DELETE /api/v1/providers/{provider_id} below), so log out first to remove one.
| Parameter | In | Type | Description |
|---|---|---|---|
provider | body | string | Managed provider name. Default managed:kimi-code |
On success, data is { logged_out: true, provider }.
GET /api/v1/oauth/usage
Plan quota and booster wallet of the managed account, fetched live from the account service. An upstream failure does not fail the envelope — it comes back in-band with kind: "error".
| Parameter | In | Type | Description |
|---|---|---|---|
provider | query | string | Managed provider name. Default managed:kimi-code |
On success, data is { kind: "ok", quota } or { kind: "error", message, status? }, where status is the upstream HTTP status when one exists. In the ok shape, quota is { usages, extraUsage }: usages carries one { usedRatio, resetAt? } entry per quota window the account has — limit5h, limit7d, monthTotal, monthCode — with usedRatio as a 0–1 float and resetAt as an RFC3339 reset timestamp, and clients render whichever entries are present; extraUsage (nullable) is the pay-as-you-go wallet: { balanceCents, totalCents, monthlyChargeLimitEnabled, monthlyChargeLimitCents, monthlyUsedCents, currency }.
GET /api/v1/oauth/userinfo
Profile of the managed account, with the same in-band kind: "error" convention as GET /api/v1/oauth/usage.
| Parameter | In | Type | Description |
|---|---|---|---|
provider | query | string | Managed provider name. Default managed:kimi-code |
On success, data is { kind: "ok", userInfo } or { kind: "error", message, status? }. userInfo always carries userId, nickname, status, region, userLevel, userLevelName, domain, and domainName, and may add globalId, bio, avatar, username, email, phone ({ countryCode, number }), createdTime, and lastLoginTime.
GET /api/v1/oauth/region
Resolves which Kimi region this client belongs to. The answer is derived locally, not probed over the network: an OAuth host pinned by environment or config wins first, then the configured OAuth key, then the region marker file in the home directory; the default is mainland-cn.
On success, data is { region } with region one of mainland-cn / global.
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 |
GET /api/v1/config
Returns the resolved global configuration — the effective result of config.toml plus overlays. Secrets are redacted: each provider reports only has_api_key, never the stored key.
On success, data is the config object; its fields mirror the top-level domains documented under Top-level fields:
| Field | Type | Description |
|---|---|---|
providers | object | Map of provider id → { type, base_url?, default_model?, has_api_key } |
|