# Sol API Reference

This document describes the live HTTP APIs behind Sol-37 as they exist on this machine on 2026-04-17.

It is intentionally implementation-grounded:

- endpoint names match the current handlers in `random/bin`
- examples use the real public and local URI shapes
- limits and edge cases come from the running code, not from a wish-list schema

## API Surface Map

Public base:

- `https://sol.system42.one`

Local service origins:

- `http://127.0.0.1:8895` for chat
- `http://127.0.0.1:8892` for knowledge search
- `http://127.0.0.1:8890` for logbook transport

Public reverse-proxied routes:

- `https://sol.system42.one/api/chat`
- `https://sol.system42.one/api/chat/query`
- `https://sol.system42.one/api/chat/health`
- `https://sol.system42.one/api/chat/history`
- `https://sol.system42.one/api/chat/reset`
- `https://sol.system42.one/api/chat/speak`
- `https://sol.system42.one/api/knowledge/query`
- `https://sol.system42.one/api/knowledge/health`
- `https://sol.system42.one/api/logbook/messages`

Related machine-readable schemas:

- `https://sol.system42.one/chat-openapi.json`
- `https://sol.system42.one/knowledge-openapi.json`
- `https://sol.system42.one/privacy.html`

Related MCP connector surface:

- `https://sol.system42.one/api/mcp/sol`
- `https://sol.system42.one/api/mcp/sol/health`

Authentication:

- none

CORS:

- chat and knowledge explicitly send permissive CORS headers
- logbook supports same-origin browser use and preflight, but should still be treated as a site API rather than a generic public cross-origin service

MCP note:

- Sol also exposes a read-only ChatGPT App / MCP connector that fronts the same chat and knowledge stack.
- The ChatGPT App registration target is the root MCP endpoint `https://sol.system42.one/api/mcp/sol`.
- The legacy SSE endpoint `https://sol.system42.one/api/mcp/sol/sse` exists for compatibility but is not the preferred app-registration URL.

## Response Conventions

Patterns used across the stack:

- success responses are JSON unless the endpoint is SSE or audio
- errors usually return `{"error": "<code>"}` and sometimes `detail`
- no route here uses bearer tokens or cookies today
- the chat and knowledge APIs are read-mostly; only chat session reset and logbook message creation mutate state

## 1. Sol Chat API

Implementation:

- handler: `/home/david/random/bin/sol_chat_api.py`
- public schema: `https://sol.system42.one/chat-openapi.json`

Purpose:

- grounded chat over Sol knowledge
- optional session persistence
- optional image input
- optional page-context grounding
- native live-site-state grounding for greetings, diagnostics, and server/status questions
- speech synthesis
- health and history inspection

### 1.1 `GET /api/chat/query`

Use this for simple text-only calls, GPT Action retrieval-style calls, or copy-pasteable direct URLs.

Accepted query parameters:

- `query` or `message`
  required text prompt
- `profile`
  optional; `reasoning`, `vision`, or `vision_fast`
- `session`
  optional session id
- `persist`
  optional boolean-ish flag; `1` persists history, default is stateless
- `page_context`
  optional JSON-encoded page context object
- `page_title`
- `page_target`
- `page_content_type`
- `page_content`
- `page_heading`
  repeatable
- `page_question`
  repeatable

Important limits:

- max message length: `12000` chars
- page context is sanitized and compacted server-side
- stateless requests get an ephemeral generated session id

Real URI examples:

- Public:
  `https://sol.system42.one/api/chat/query?query=What%20is%20Sol%3F`
- Public with session persistence:
  `https://sol.system42.one/api/chat/query?query=Summarize%20the%20current%20system%20state&session=ops-notebook&persist=1`
- Public with page grounding:
  `https://sol.system42.one/api/chat/query?query=Summarize%20this%20page&page_title=Traffic%20Monitor&page_target=%2Fsite-metrics.html&page_content_type=text%2Fhtml&page_content=The%20page%20shows%20visible%20hits%2C%20telemetry%2C%20and%20service%20status.`
- Local:
  `http://127.0.0.1:8895/api/chat/query?query=What%20is%20Sol%3F`

Example:

```bash
curl -G 'https://sol.system42.one/api/chat/query' \
  --data-urlencode 'query=What is Sol?' \
  --data-urlencode 'session=docs-example' \
  --data-urlencode 'persist=1'
```

Typical response shape:

```json
{
  "message": "Sol is a local-first cognitive annotation system that treats files and logs as the substrate for reasoning.",
  "session": "docs-example",
  "persisted": true,
  "grounded": true,
  "retrieval": {
    "used": true,
    "query": "What is Sol?",
    "warning": null,
    "grounding_mode": "normal",
    "hits": [
      {
        "rank": 1,
        "score": 0.72,
        "path": "/home/david/knowledge/sol-readme.md",
        "doc_key": "/home/david/knowledge/sol-readme.md#0",
        "chunk": 0,
        "text": "Sol is a local-first cognitive annotation system..."
      }
    ],
    "supplemental": {
      "query": "local cognition annotation system files logs reasoning",
      "used": true,
      "warning": null,
      "grounding_mode": "supplemental",
      "hits": [
        {
          "rank": 1,
          "score": 0.61,
          "path": "/home/david/knowledge/sol-architecture.md",
          "doc_key": "/home/david/knowledge/sol-architecture.md#2",
          "chunk": 2,
          "text": "Sol treats files, logs, and local state as a substrate for reasoning..."
        }
      ],
      "source_documents": []
    },
    "source_documents": [
      {
        "path": "/home/david/knowledge/sol-readme.md",
        "resolved_path": "/home/david/knowledge/sol-readme.md",
        "retrieved_snippet": "Sol is a local-first cognitive annotation system...",
        "content_mode": "full_current_file",
        "snippet_found_in_current_file": true,
        "current_file_representation": "raw_file",
        "current_file_content": "Sol is a local-first cognitive annotation system..."
      }
    ]
  },
  "page_context_applied": false,
  "history_size": 3,
  "fallback_grounding_used": false,
  "backend_profile": "reasoning",
  "backend_model_id": "DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf",
  "image_count": 0,
  "query": "What is Sol?",
  "cache_hit": false
}
```

Notes on retrieval metadata:

- `retrieval.hits` are the ranked embedding matches from the knowledge API.
- `retrieval.supplemental` is optional and appears when the chat backend generates a second knowledge query for the same turn and calls the knowledge API again with that query.
- `retrieval.supplemental.query` is generated by the chat backend itself, not supplied by the caller.
- `retrieval.supplemental` is additive context; it does not replace `retrieval.query` or the primary `retrieval.hits`.
- `retrieval.source_documents` is optional and appears when the chat backend can open the underlying current file for one or more top hits.
- `retrieval.live_site_state` is optional and can appear on ordinary text turns as additional context, not just on diagnostics or greeting probes.
- when present, `retrieval.live_site_state` can include traffic totals, top paths, sensor readouts, recent requests, and runtime service status.
- for `.html` and `.htm` files, `current_file_content` is now derived from rendered visible document text rather than raw markup, and `current_file_representation` is `rendered_html_text`
- for plain text and markdown files, `current_file_representation` is typically `raw_file`
- `snippet_found_in_current_file=true` means the retrieved snippet was found in the current readable file text, not just in the archived retrieval payload

Creative-prompt behavior:

- open-ended prompts like `tell me a story` still run normal retrieval and can use the current file content as seed material
- those turns can also carry the normal live metrics/context bundle; metrics are not removed just because retrieval is present
- the retrieval/context bundle is intended to steer and enrich the generation, not force the response into extractive mode
- the backend prompt now explicitly tells the model to treat injected current-file text as accessible turn context rather than as an unavailable external file

Real-world uses:

- create a shareable URL that answers a narrow question against Sol knowledge
- run a lightweight health probe that still exercises retrieval and generation
- ask for current server health, traffic, or sensor state and get a live-site-state answer instead of archive-heavy retrieval
- summarize the page currently open inside the shell without using a POST body
- wire a GPT Action or bookmarklet that can only issue GET requests

Failure modes:

- `400 {"error":"missing_query"}`
- `400 {"error":"message_too_long"}`
- `400 {"error":"invalid_page_context:..."}` in JSON-encoded page context failures
- `502 {"error":"chat_failed","detail":"..."}` when retrieval or model execution fails

### 1.2 `POST /api/chat`

Use this for full chat turns, multimodal turns, SSE streaming, or richer page context.

Request body fields:

- `message`
  optional if `images` is supplied
- `session`
  optional
- `persist`
  boolean, default effectively `true` for POST requests
- `stream`
  boolean; if true the response is SSE
- `profile`
  optional; `reasoning`, `vision`, `vision_fast`
- `page_context`
  object with:
  - `target`
  - `title`
  - `content_type`
  - `headings`
  - `suggested_questions`
  - `content`
- `images`
  list of image objects
  - `name`
  - `mime_type`
  - `data_url`

Image requirements:

- image input must currently be a base64 data URL
- max images per request: `3` by default
- max bytes per image: `10485760`
- non-data-URL image inputs are rejected with `invalid_image_data`

Real URI examples:

- Public JSON turn:
  `https://sol.system42.one/api/chat`
- Local JSON turn:
  `http://127.0.0.1:8895/api/chat`

Example: standard JSON chat turn

```bash
curl 'https://sol.system42.one/api/chat' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Give me a grounded two-paragraph explanation of Sol.",
    "session": "docs-post-example",
    "persist": true,
    "stream": false
  }'
```

Example: page-aware assistant turn

```bash
curl 'https://sol.system42.one/api/chat' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Summarize this page and suggest two follow-up questions.",
    "session": "page-summary-demo",
    "persist": false,
    "page_context": {
      "target": "/site-metrics.html",
      "title": "Traffic Monitor",
      "content_type": "text/html",
      "headings": ["Visible Hits", "Archive Counters", "Telemetry"],
      "suggested_questions": ["What changed recently?", "Are any services down?"],
      "content": "This page shows current visible traffic, archive counters, and mirrored service telemetry."
    }
  }'
```

Example: multimodal POST

```bash
curl 'https://sol.system42.one/api/chat' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Describe the visible scene and call out anything unusual.",
    "profile": "vision_fast",
    "persist": false,
    "images": [
      {
        "name": "dashboard-camera.jpg",
        "mime_type": "image/jpeg",
        "data_url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
      }
    ]
  }'
```

Example: SSE streaming

```bash
curl 'https://sol.system42.one/api/chat' \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{
    "message": "Explain how the knowledge API and chat API fit together.",
    "session": "stream-demo",
    "stream": true
  }'
```

Streaming event types:

- `status`
- `delta`
- `done`
- `error`

Real-world uses:

- power the `/chat` page
- power the floating desktop assistant
- ask page-specific questions against the current shell surface
- caption screenshots or dashboard camera frames
- run a stateless classification/explanation turn without polluting saved history

### 1.3 `GET /api/chat/health`

Use this to verify chat runtime readiness, model routing, and voice availability.

Real URI examples:

- Public:
  `https://sol.system42.one/api/chat/health`
- Local:
  `http://127.0.0.1:8895/api/chat/health`

Example:

```bash
curl 'https://sol.system42.one/api/chat/health'
```

Current live response fields include:

- `ok`
- `strict_grounding`
- `backend_configured`
- `backend_base_url`
- `backend_model_id`
- `backend_profile`
- `voice_available`
- `tts_cache_enabled`
- `stack`
- `backends`

Real-world uses:

- confirm whether the reasoning lane is alive before routing users into `/chat`
- inspect whether voice playback is available
- show a dashboard card with active model and managed backends

### 1.4 `GET /api/chat/history`

Loads sanitized message history for a persisted session.

Required query parameter:

- `session`

Real URI examples:

- Public:
  `https://sol.system42.one/api/chat/history?session=docs-example`
- Local:
  `http://127.0.0.1:8895/api/chat/history?session=docs-example`

Example:

```bash
curl -G 'https://sol.system42.one/api/chat/history' \
  --data-urlencode 'session=docs-example'
```

Response shape:

```json
{
  "session": "docs-example",
  "messages": [
    {"role": "system", "content": "..."},
    {"role": "user", "content": "What is Sol?"},
    {"role": "assistant", "content": "Sol is a local-first cognitive annotation system..."}
  ],
  "assistant_debug": [
    {
      "grounded": true,
      "fallback_grounding_used": false,
      "page_context_applied": false,
      "backend_profile": "reasoning",
      "backend_model_id": "DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf"
    }
  ]
}
```

### 1.5 `POST /api/chat/reset`

Resets a persisted session.

Request body:

- `session`

Real URI examples:

- Public:
  `https://sol.system42.one/api/chat/reset`
- Local:
  `http://127.0.0.1:8895/api/chat/reset`

Example:

```bash
curl 'https://sol.system42.one/api/chat/reset' \
  -H 'Content-Type: application/json' \
  -d '{"session":"docs-example"}'
```

Response:

```json
{"ok": true, "session": "docs-example"}
```

### 1.6 `GET` or `POST /api/chat/speak`

Synthesizes reply text to MP3 using the configured voice backend.

Input:

- GET query parameter `text`
- or POST JSON body `{"text":"..."}`

Output:

- `audio/mpeg`

Real URI examples:

- Public GET:
  `https://sol.system42.one/api/chat/speak?text=Hello%20from%20Sol`
- Local GET:
  `http://127.0.0.1:8895/api/chat/speak?text=Hello%20from%20Sol`
- Public POST target:
  `https://sol.system42.one/api/chat/speak`

Examples:

```bash
curl -L 'https://sol.system42.one/api/chat/speak?text=Hello%20from%20Sol' \
  --output sol-hello.mp3
```

```bash
curl 'https://sol.system42.one/api/chat/speak' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Read this aloud in Sol voice."}' \
  --output sol-voice.mp3
```

Behavior notes:

- the server caches generated speech by prompt text and voice settings
- GET can stream bytes directly
- POST returns the synthesized bytes in one response

Common errors:

- `400 {"error":"missing_text"}`
- `400 {"error":"text_too_long"}`
- `503 {"error":"voice_unavailable"}`
- `502 {"error":"voice_failed:..."}` or related voice backend failures

## 2. Knowledge Query API

Implementation:

- handler: `/home/david/random/bin/knowledge_query_api.py`
- public schema: `https://sol.system42.one/knowledge-openapi.json`

Purpose:

- read-only semantic retrieval over the Sol knowledge cache
- no mutation
- intended as a retrieval primitive, not a final-answer API

Backing behavior:

- loads a cached embedding index from disk
- auto-reloads when the cache file mtime changes
- can fall back to `local-hash` if the primary backend fails or times out
- rejects `POST /query` with `405`

### 2.1 `GET /api/knowledge/query`

Required query parameters:

- `query`

Optional query parameters:

- `top_k`
  integer, clamped to `1..12`, default `5`

Hard limits:

- query length max: `2000` chars

Real URI examples:

- Public:
  `https://sol.system42.one/api/knowledge/query?query=How%20does%20the%20knowledge%20API%20work%3F&top_k=2`
- Local:
  `http://127.0.0.1:8892/api/knowledge/query?query=How%20does%20the%20knowledge%20API%20work%3F&top_k=2`

Examples:

```bash
curl -G 'https://sol.system42.one/api/knowledge/query' \
  --data-urlencode 'query=How does the knowledge API work?' \
  --data-urlencode 'top_k=2'
```

```bash
python3 - <<'PY'
import json, urllib.parse, urllib.request
base = 'https://sol.system42.one/api/knowledge/query'
params = urllib.parse.urlencode({'query': 'MasterBot voice playback trials', 'top_k': 3})
with urllib.request.urlopen(f'{base}?{params}') as r:
    data = json.load(r)
print(json.dumps(data, indent=2)[:2000])
PY
```

Typical response shape:

```json
{
  "query": "How does the knowledge API work?",
  "top_k": 2,
  "backend": "openai",
  "cache_path": "/home/david/logs/sol_embeddings.openai.pkl",
  "warning": null,
  "results": [
    {
      "rank": 1,
      "score": 0.83,
      "path": "/home/david/knowledge/sol-site-server.md",
      "chunk": 4,
      "doc_key": "/home/david/knowledge/sol-site-server.md#4",
      "text": "Serves a read-only semantic query endpoint..."
    }
  ]
}
```

Fallback semantics:

- when the primary backend fails or times out, the response may include:
  - `requested_backend`
  - `warning`
- example warning values:
  - `primary_backend_failed:SomeError`
  - `primary_backend_timeout:8.0s`

Real-world uses:

- power GPT Actions that need retrieval but not synthesis
- inspect relevant source chunks before asking the chat API to summarize them
- build search boxes inside the shell
- do linkable knowledge lookups with plain query-string URLs

Common errors:

- `400 {"error":"missing_query"}`
- `400 {"error":"invalid_top_k"}`
- `400 {"error":"query_too_long"}`
- `503 {"error":"cache_missing","detail":"..."}`
- `502 {"error":"search_failed","detail":"..."}`

Method rule:

- `POST /api/knowledge/query` returns `405 Method Not Allowed`
- `Allow: GET, OPTIONS`

### 2.2 `GET /api/knowledge/health`

Use this to check that the index is loaded and inspect query-cache metadata.

Real URI examples:

- Public:
  `https://sol.system42.one/api/knowledge/health`
- Local:
  `http://127.0.0.1:8892/api/knowledge/health`

Example:

```bash
curl 'https://sol.system42.one/api/knowledge/health'
```

Current live response fields:

- `ok`
- `backend`
- `cache_path`
- `knowledge_dir`
- `docs`
- `query_cache_path`
- `query_cache_entries`

Important caveat:

- `health` only proves the cache is loaded
- it does not guarantee that live query embeddings will succeed against the primary backend

## 3. Public Logbook API

Implementation:

- handler: `/home/david/random/bin/public_logbook_api.py`
- transport helpers: `/home/david/random/bin/logbook_irc_common.py`

Purpose:

- read and append public IRC-backed logbook messages for the Sol shell

Public route shape:

- public: `https://sol.system42.one/api/logbook/messages`
- local service: `http://127.0.0.1:8890/messages`

Allowed channels:

- `public-logbook`
- `archive-watch`
- `civilization-sim`

Unknown channels are normalized back to `public-logbook`.

### 3.1 `GET /api/logbook/messages`

Query parameters:

- `limit`
  optional, default `80`, clamped to `1..250`
- `channel`
  optional, default `public-logbook`

Real URI examples:

- Public latest 20:
  `https://sol.system42.one/api/logbook/messages?limit=20`
- Public alternate channel:
  `https://sol.system42.one/api/logbook/messages?channel=archive-watch&limit=50`
- Local:
  `http://127.0.0.1:8890/messages?limit=2`

Example:

```bash
curl -G 'https://sol.system42.one/api/logbook/messages' \
  --data-urlencode 'channel=public-logbook' \
  --data-urlencode 'limit=10'
```

Typical response:

```json
{
  "messages": [
    {
      "id": "1774482631974-f5d49e",
      "name": "davidlones",
      "message": "still alive",
      "created_at": "2026-03-25T23:50:31.974201Z",
      "channel": "public-logbook"
    }
  ],
  "limit": 10,
  "channel": "public-logbook"
}
```

Real-world uses:

- render the public logbook program inside the shell
- build a “latest transmissions” widget
- watch a specific public channel without joining IRC directly

### 3.2 `POST /api/logbook/messages`

Creates a logbook message by sending through the IRC-backed transport.

JSON body:

- `name`
- `message`
- `channel`
  optional, defaults to `public-logbook`

Request size rules:

- `Content-Length` must be `1..8192`
- invalid JSON is rejected

Rate limiting:

- per-client IP
- `4` requests per `300` seconds

Real URI examples:

- Public:
  `https://sol.system42.one/api/logbook/messages`
- Local:
  `http://127.0.0.1:8890/messages`

Example:

```bash
curl 'https://sol.system42.one/api/logbook/messages' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Sol37",
    "message": "Archive sync complete.",
    "channel": "archive-watch"
  }'
```

Success response:

```json
{
  "ok": true,
  "message": {
    "name": "Sol37",
    "message": "Archive sync complete.",
    "channel": "archive-watch"
  }
}
```

Common errors:

- `400 {"error":"invalid_length"}`
- `400 {"error":"invalid_json"}`
- `400 {"error":"name_required"}`
- `400 {"error":"message_required"}`
- `409 {"error":"name_in_use"}`
- `429 {"error":"rate_limited"}`
- `502 {"error":"..."}`

Real-world uses:

- allow the in-browser logbook client to post messages
- bridge lightweight site events into public channels
- provide a public-but-rate-limited append-only note path without exposing direct IRC credentials

## 4. Internal-Only Related API

`local_dashboard_api.py` exists and is important to the Sol stack, but it is not part of the general public external API contract in the same way as chat, knowledge, and logbook.

It should currently be treated as an internal service dependency for:

- dashboard telemetry
- camera captioning
- local launch actions
- service console state
- LAN/local gating behavior

If you want that surfaced as a stable public API, it should be documented separately as an internal operations API first, then promoted deliberately.

## 5. Real Integration Recipes

### Recipe: direct retrieval, then grounded answer

1. Call:
   `GET https://sol.system42.one/api/knowledge/query?query=How%20does%20the%20knowledge%20API%20work%3F&top_k=3`
2. Inspect the returned `results`
3. Call:
   `POST https://sol.system42.one/api/chat`
4. Ask for a synthesis or explanation if you want a human-readable answer

Why this is useful:

- the knowledge API gives raw evidence
- the chat API gives interpretation, synthesis, and conversational framing

### Recipe: page-aware shell assistant

1. Capture the current shell page title, target, headings, and compact text
2. Send them in `page_context` to `POST /api/chat`
3. Ask for summary, explanation, or navigation help

Why this is useful:

- it keeps the assistant anchored in the current UI state
- it avoids forcing retrieval when the page itself already contains the needed context

### Recipe: persistent multi-turn operator session

1. Start with:
   `POST /api/chat` using `{"session":"ops-1","persist":true}`
2. Continue with the same session id
3. Read back state with:
   `GET /api/chat/history?session=ops-1`
4. Reset with:
   `POST /api/chat/reset`

### Recipe: voice playback for a finished assistant answer

1. Get a response from `/api/chat` or `/api/chat/query`
2. Take the `message` text
3. Feed it to `/api/chat/speak`
4. Save the MP3 or stream it directly to the browser

### Recipe: public logbook monitor

1. Poll:
   `GET /api/logbook/messages?channel=public-logbook&limit=20`
2. Render newest entries
3. Post new lines only when needed, respecting the rate limit

## 6. Verification Notes

These docs were checked against the current implementation and local contract tests:

- `python3 /home/david/random/bin/check_knowledge_query_api_contract.py`
- `python3 /home/david/random/bin/check_sol_chat_api_contract.py`

They also reflect current live local responses from:

- `http://127.0.0.1:8895/api/chat/health`
- `http://127.0.0.1:8892/api/knowledge/health`
- `http://127.0.0.1:8890/messages?limit=2`
