> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xynthis.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP API

> The local daemon on port 3939 and the model server's OpenAI-compatible surface on port 8080.

Xynthis exposes two local HTTP surfaces. The daemon (`xynthis serve`) wraps the agent loop and the brain on `http://127.0.0.1:3939`; this is what the macOS app talks to. The model server (`xynthis-llm-serve`) exposes the on-device model on `http://127.0.0.1:8080` behind a standard OpenAI-compatible API. Both bind to loopback by default and have no authentication; do not expose them beyond the machine.

## The daemon (127.0.0.1:3939)

Start it with `xynthis serve [--port 3939]`. Error handling follows one convention: when the brain is unreachable, handlers return HTTP 200 with an error envelope (`{"op": "error", "message": "brain daemon offline"}` or `{"ok": false, "error": "..."}` depending on the route), so clients poll one shape.

### POST /api/chat

Runs one agent turn. Blocks until the turn finishes.

```json theme={null}
{
  "message": "rename the README",
  "sessionId": "b3f2c9e1-...",
  "tier": "pro",
  "guide_mode": false,
  "cwd": "/Users/me/project"
}
```

Only `message` is required. `sessionId` is a stable per-conversation id; the daemon maps each one onto its own on-disk session so chat threads keep separate histories. Ids are restricted to `[A-Za-z0-9_-]`, 1-128 characters; anything else is ignored and the daemon stays on its current session. `tier` (`"free"` / `"pro"` / `"team"`) sets the memory-recall window for the turn: an explicit `"free"` caps recall to the last 7 days; `"pro"`, `"team"`, and absent mean unlimited. `cwd` binds the agent's file tools and `bash` to a project root for the turn. A request with `fleet_mode: true` and `tier: "free"` is rejected with HTTP 402: multi-agent execution is Pro/Team only.

Response:

```json theme={null}
{
  "text": "Done — renamed README.md to README.old.md",
  "tools": [
    { "name": "bash", "args": { "command": "mv README.md README.old.md" }, "result": "..." }
  ],
  "session_id": "b3f2c9e1-...",
  "iterations": 3
}
```

`iterations` is how many LLM round-trips the agent ran. `POST /api/chat/stop` cancels an in-flight turn.

### GET /api/status

Returns the brain's status JSON: uptime, perception counters, last dream, working-memory size, knowledge-graph size. Responses are cached for 1.5 seconds server-side, sized for the app's 3-second poll; offline errors are never cached, so recovery shows immediately.

### GET /api/events

A Server-Sent Events stream of agent activity. Each event is a JSON object tagged with an `event` field in snake\_case: `turn_started`, `llm_call_started`, `llm_call_completed`, `tool_called`, `tool_result`, `tool_denied`, `memory_recalled`, `memory_perceived`, `recall_judged`, `turn_completed`, and others. Every event carries a `ts` unix timestamp. A consumer that falls behind receives `{"event": "lagged", "dropped": N}` and the stream continues; the daemon never drops a slow client. Streams end when the daemon shuts down.

### GET /api/recall

```
GET /api/recall?q=<query>&top_k=<n>&tier=<tier>
```

Runs the brain's layered retriever and returns its hits. `tier=free` caps results to the 7-day window, same as chat.

### POST /api/remember

```json theme={null}
{ "content": "text to store", "kind": "note" }
```

Stores a perception in the brain. `kind` defaults to `"note"`.

### GET /api/sessions

Lists saved chat sessions, newest first. Sessions with zero messages are omitted.

```json theme={null}
{
  "sessions": [
    { "id": "...", "title": "...", "created_at": 1760000000, "updated_at": 1760000100, "message_count": 12 }
  ]
}
```

### GET /api/session/transcript

```
GET /api/session/transcript?id=<session-id>
```

Returns the clean user/assistant transcript of a session: `{"id": "...", "title": "...", "messages": [{"role": "user", "text": "..."}]}`. Internal scaffolding (injected memory context, working-memory blocks, tool-result turns) is stripped, so the messages are exactly what a chat UI should render.

### GET /api/llm/list

Every provider configured in `config.toml`, with availability and the active marker. Powers the app's model picker.

```json theme={null}
{
  "providers": [
    {
      "name": "anthropic",
      "kind": "anthropic-messages",
      "model": "claude-sonnet-4-5",
      "lite": false,
      "base_url": "https://api.anthropic.com/v1/messages",
      "launch_command": null,
      "available": true,
      "local": false,
      "active": true
    }
  ],
  "active_provider": "anthropic",
  "active_model": "claude-sonnet-4-5"
}
```

`available` comes from the credential scanner for remote providers; for local providers it is a live TCP probe of the configured port, so a configured-but-down local server shows as unavailable. Providers are ordered active first, then available, then alphabetical.

### POST /api/llm/set

```json theme={null}
{ "provider": "ollama", "model": "llama3.3" }
```

Validates that the provider exists, sets `[models] active` in `config.toml`, and saves. `model` is optional; it falls back to the provider's `default_model`. Returns `{"ok": true, "active": "ollama/llama3.3", "active_provider": "...", "active_model": "...", "active_base_url": "..."}` or `{"ok": false, "error": "..."}`.

### GET /api/extensions

Returns a snapshot of the loaded external MCP/webhook extensions: the same payload the agent's own `extension_list` tool sees.

### Other routes

The daemon exposes further routes the app consumes: corpus management (`/api/corpus`, `/api/corpus/add|remove|watch|update`), permissions (`/api/permissions`, `/api/permissions/set|preset`), lessons, semantic search (`POST /api/search/semantic`), cortex, knowledge graph (`/api/kg/graph`), auth scanning (`/api/auth/scan|apply`), and voice transcription. They follow the same envelope conventions; the routes above are the stable integration surface.

## The model server (127.0.0.1:8080)

`xynthis-llm-serve` exposes the on-device model over the OpenAI chat-completions standard, so any OpenAI-compatible client works against it unchanged. Port and bind address come from `XYNTHIS_LLM_PORT` (default 8080) and `XYNTHIS_LLM_BIND` (default `127.0.0.1`). Only bind beyond loopback behind a tunnel or VPN.

### GET /health

Returns `ok`. Liveness probe.

### GET /status

```json theme={null}
{ "model_id": "xynthis-local", "serving": "teacher_backbone", "vocab_size": 49152, "jspace": false }
```

`serving` is `"student"` once a fine-tuned LoRA adapter is attached, otherwise `"teacher_backbone"`. `jspace` reports whether workspace capture is on for this process.

### GET /v1/models

Advertises the model id (`xynthis-local` by default, override with `XYNTHIS_LLM_MODEL_ID`). Also reachable as `/models`; the `/v1` prefix is optional on every route.

### POST /v1/chat/completions

Standard OpenAI request body: `model`, `messages` (array of `{role, content}`), `temperature`, `top_p`, `max_tokens`, `stream`. One extension field:

* `session_id` (string, optional): treat successive requests with the same id as one conversation. The server keeps the KV cache per session and reuses it when the new prompt extends the previous turn, which skips re-processing the shared prefix. Idle sessions are evicted after 30 minutes.

With `"stream": true` the response is SSE: one chunk per token delta, then a finish chunk and `data: [DONE]`.

### The xynthis\_jspace field

When the server runs with `XYNTHIS_JSPACE=1`, every chat response carries an additive `xynthis_jspace` block: the reply's dominant concepts, read out from the model's own activations while it generated:

```json theme={null}
"xynthis_jspace": {
  "concepts": [
    { "label": "rust", "weight": 0.31, "rank": 1 },
    { "label": "memory", "weight": 0.18, "rank": 2 }
  ],
  "generated_positions": 42
}
```

Non-streaming responses carry it as a top-level field; streaming responses attach it to the final finish chunk. Standard OpenAI clients ignore the unknown field. Capture is off by default and costs one top-k softmax per generated token when on. See [Workspace readouts](/llm/workspace-readouts).
