# Local session HTTP API

> Start a loopback Code server, create trusted sessions, submit messages, follow resumable events, and answer permissions using the local session API.

Source: https://kaitoi.io/docs/cli/session-api/
Section: Kaitoi CLI / Editors and automation
Written for: Developers embedding Code in local tools.
Prerequisites: Kaitoi CLI installed and a configured provider
Last reviewed: 2026-10-24

---

`kaitoi code serve` exposes ongoing Code sessions to local clients. It runs on
IPv4 loopback and uses its own bearer token. It is separate from the hosted
Kaitoi API and the recipe [Runtime API](/docs/cli/runtime-api/). This guide
covers protocol `kaitoi-code-serve/1` in CLI **0.9.0**.

## Start and discover the server

```bash
kaitoi code serve
# Or choose a fixed port:
kaitoi code serve --port 8767
```

The default port `0` chooses a free port. Binding is always `127.0.0.1`;
there is no host option. You can set `--provider`, `--model`, and
`--token-file PATH`.

Without a token file, the server creates a local token and writes its address
and token to `~/.kaitoi/code/serve/<pid>.json`, then removes the record when it
stops. Protect that record. With `--token-file`, supply an existing protected
token file; **no discovery record is written**, so configure the client address
and token explicitly.

Every route—including `/v1/health`—requires `Authorization: Bearer <local-token>`.
The Host must be `127.0.0.1:<port>` or `localhost:<port>`, and requests must have
**no Origin header**. There is no browser CORS or WebSocket interface. An account
API key is not a substitute for this local control token.

## Create a session and follow one turn

Create a session with an absolute workspace and an explicit trust level:

```json
{
	"workspace": "/absolute/path/to/project",
	"trust": "read",
	"mode": "plan"
}
```

Trust is `none`, `read`, or `edits`. Optional fields include `provider`, `model`,
`resume`, and `commands`. Command availability is not blanket command approval.
POST a message such as `{"text":"Explain this project"}` to the returned
session's messages route. The 202 response gives `turnId` and `after`; use the
cursor to follow that turn without missing its first events.


A client creates a served session, submits a message, and follows its events
from the returned cursor. Permission decisions travel back on a separate
request. The turn ends, but the session-wide SSE connection can remain open
for the next turn. Cancellation stops current work; closing deletes the served
session while preserving its saved log.

A continuous session event line crosses the end of turn 1 and the start of turn 2. Reconnecting after the last received ID follows that existing stream, rather than submitting the message again.

*Figure: One served session carries multiple turns; turn.end ends a turn, not the session stream.*

## Route reference

Paths below are relative to the local server. Replace `{id}` and
`{permissionId}` with returned identifiers.

| Method and path                                     | Purpose                                                        |
| --------------------------------------------------- | -------------------------------------------------------------- |
| `GET /v1/health`                                    | Authenticated health, version, and protocol.                   |
| `GET /v1/sessions`                                  | List open served sessions.                                     |
| `POST /v1/sessions`                                 | Create; returns 201.                                           |
| `GET /v1/sessions/{id}`                             | Inspect a session.                                             |
| `DELETE /v1/sessions/{id}`                          | Stop and close it; saved log remains.                          |
| `POST /v1/sessions/{id}/messages`                   | Send `{text, images?}`; image paths are absolute; returns 202. |
| `GET /v1/sessions/{id}/events`                      | Session-wide SSE, with `after` query or `Last-Event-ID`.       |
| `POST /v1/sessions/{id}/permissions/{permissionId}` | Answer a pending request with `{decision}`.                    |
| `POST /v1/sessions/{id}/cancel`                     | Request current-turn cancellation with `{}`.                   |
| `POST /v1/sessions/{id}/mode`                       | Set `{mode}` for the next turn: plan, build, or crew.          |

Without a cursor, the event stream starts with future events. It sends heartbeat
comments every 15 seconds and can span several turns. `turn.end` marks a single
turn's end, so a client should not wait for the whole SSE connection to close.

## Handle permissions and lifecycle

Answer only actual pending permission IDs and use their advertised decisions.
Most offer `allow_once`, `allow_session`, and `deny`; Crew and media accept only
one-call approval or denial. Pending requests time out after 600 seconds.

A session runs one turn at a time. New messages can return 409 `turn_running`
or `turn_stopping`. Cancel or close stops the command tree before another turn
can start; cancellation may remain in progress. It is not a rollback of work
already completed. Use the [SDK](/docs/cli/python-sdk/) for a synchronous Python
client that handles the per-turn stream boundary.
