Backend API Documentation
Base URL: http://127.0.0.1:8000
Authentication
The browser uses an application session cookie. Except for GET /,
POST /api/auth/login, and GET /api/auth/status, API routes require a
valid session and a role allowed for that route. Cookie-authenticated
state-changing requests require X-CSRF-Token from /api/auth/status and an
allowed Origin. Browser clients must use credentials: "include".
The interactive API documentation (/docs, /redoc) and schema
(/openapi.json) are disabled by default and return 404, because they
describe the whole API to anonymous callers. Set
AI_SERVER_ENABLE_API_DOCS=true only for local development. Container health
probes use GET / instead of /openapi.json.
POST /api/auth/login
{
"provider": "local",
"username": "alice",
"password": "..."
}
Use "provider": "proxmox" for Proxmox VE credentials. A successful login
sets an HttpOnly application-session cookie and returns identity, role, and a
CSRF token. Proxmox users without an explicit local role mapping are denied.
Credential failures return a generic error.
GET /api/auth/status returns the authenticated identity, role, and CSRF
token; unauthenticated clients receive {"authenticated": false}.
POST /api/auth/logout revokes the current session and clears its cookie.
The Discord bot uses a separate bearer credential and is limited to its machine API routes. It does not use or receive a dashboard user's session. The normal development backend is loopback-only. Optional Windows mobile testing uses the dedicated LAN launcher with HTTPS on a single configured private IPv4 address, an exact configured CORS origin, and Secure cookies; this does not replace production HTTPS proxy and firewall validation. See Authentication & Session Management and the mobile LAN testing guide.
Authorization roles
viewer: read-only status, models, and observe-only diagnostic history.operator: viewer access plus chat/research, model selection, and manual diagnostics observations, including explicit diagnostic requests in chat.admin: operator access plus backend logs, bot restart, and management of explicitly published shared admin insights and the shared AI Interface.
The local CLI manages accounts and explicit Proxmox identity-to-role mappings. The API does not expose account administration.
Release-bundle verification API — verify and stage only
These admin-only routes let an administrator upload a signed release bundle and have the server verify it. They require the normal session, allowed Origin, and CSRF protections. They never apply, extract into the live deployment, or execute anything from the bundle.
GET /api/admin/updates—{enabled, apply_supported: false, staged, limits}.enabledisfalseuntilAI_SERVER_UPDATE_PUBLIC_KEYSis configured. Expired staged releases are purged on each call.POST /api/admin/updates— body is the raw.aibundle(Content-Type: application/octet-stream, streamed to a private incoming file with a hard size cap). Returns201and the staged record (release_id,version,created_at,signer,payload_sha256,payload_size,file_count,unpacked_bytes,staged_at,expires_at,status: "verified"). Errors:503not configured,400empty/invalid length,409busy or already staged,413too large,422failed verification (the message names the failed check, never file contents),507insufficient disk space.GET /api/admin/updates/{release_id}— one staged record, or404.DELETE /api/admin/updates/{release_id}— discard a staged release.
Verification requires, in order: exactly the three expected outer members as
regular files; an Ed25519 signature over the raw manifest bytes by a key in
AI_SERVER_UPDATE_PUBLIC_KEYS; a manifest with exactly the supported fields
and in canonical JSON form (requires_data_migration must be false); a
payload whose size and SHA-256 match the signed manifest; and a payload tar
that has no links, special files, absolute or traversing paths, duplicates,
secrets or database/key files, excessive entries or expansion, and that
contains Containerfile and ai-backend/main.py. Staged releases live in a
private directory (default beside the auth database, updates/) for
AI_SERVER_UPDATE_EXPIRY_HOURS.
The .aibundle format is for the allow-listed application source tree only; it
does not carry GGUF model weights. Model files live separately in the persistent
AI_SERVER_MODEL_DIR and can be much larger than the current 128 MiB upload and
256 MiB unpacked limits. A separate signed model-package flow is planned for
admin-managed installation into that directory. It must verify model metadata
and content independently, preserve existing model files, and remain separate
from application-release rollback. No model upload or installation API exists
yet.
There is deliberately no POST /api/admin/updates/{release_id}/apply route;
it returns 403 through the dashboard role gate. The apply step, a separate
least-privilege updater (with its rootless-versus-brokered privilege model
still undecided), and an OCI image payload remain design targets (see the
Linux/Proxmox transition guide). Known limit: nothing yet prevents replaying
an older validly signed bundle (no rollback/downgrade protection beyond the
operator's judgement).
System Endpoints
GET /api/system
Requires the viewer role.
Returns the backend host's node_name, CPU and RAM telemetry, plus GPU load.
On Windows, gpu is the live
utilization of the busiest engine on the detected AMD discrete GPU. The
response includes gpu_adapter, gpu_engine, gpu_status, and gpu_error;
gpu is null when the live reading is unavailable. vram_used and
vram_total report live dedicated-memory usage and device-local capacity in
GiB; they are null if GPU telemetry is unavailable. On Linux with readable
AMD amdgpu sysfs counters, gpu is whole-device utilization and
gpu_engine is "overall"; utilization and VRAM are null with an explicit
unavailable status if the required counters cannot be read or adapter
selection is ambiguous. This telemetry does not assert ROCm/HIP availability.
activity_mode is
diagnostics while a read-only observation is running, or null while idle.
The dashboard gives this active work precedence over its local/research chat
selection. maintenance is recognized by the dashboard but is not currently
reported because a maintenance lifecycle has not been implemented.
GET /api/gpu
Requires the viewer role.
Returns the GPU load and telemetry status fields from /api/system.
Diagnostics (Observe-only)
Diagnostics are explicitly triggered and read-only. No scheduler, repair
action, or command execution is exposed. Observations, incident state, and
audit events are stored in the local SQLite database configured by
AI_SERVER_MEMORY_DB_PATH and survive backend restarts. The database retains
the latest 200 observations, the latest 1,000 audit events, and all open
incidents plus up to 500 resolved incidents. These endpoints are available to
the viewer role. Running an observation requires the operator role.
Resource threshold state is evaluated only when an observation is explicitly
run. CPU >=90% and RAM >=90% must persist across observations for 120 seconds;
log-volume free space <=10% must persist for 60 seconds. Recovery requires
CPU <=80%, RAM <=85%, or free space >=15%, respectively, for 60 seconds.
Observations more than 300 seconds apart reset pending/recovery duration
confirmation. These sampled intervals are evidence only and do not establish
continuous conditions between observations.
GET /api/diagnostics/status
Returns mode (observe_only), storage (sqlite), the latest
observation and status (not_run, healthy, degraded, or unknown), and
incident counts.
GET /api/diagnostics/observations?limit=20 Returns recent observations, limited to 1–100 items.
GET /api/diagnostics/incidents?status=all&limit=100
Returns recent incidents filtered by all, open, or resolved, limited to
1–500 items.
GET /api/diagnostics/report?observation_id=obs_... Requires the viewer role. Returns a JSON evidence report for a retained observation, including its health checks, resource snapshot, threshold evidence, and related incidents as they existed at that observation. Historical incident fields are reconstructed from the selected observation's degraded checks and active threshold evidence, with incident metadata used only when its recorded interval contains the observation. Unknown or expired observation IDs return 404. Report generation reads recorded in-memory history only: it does not run probes, trigger a new observation, or write persistent state. The dashboard's Download report action downloads the latest recorded observation.
POST /api/diagnostics/observe Runs the configured health checks and resource sampling once, records the observation and incident transitions, and returns the observation. It does not perform remediation. The dashboard exposes this action through its Diagnostics panel's Run observation button. It requires the operator role. An operator can also explicitly ask the dashboard chat to run/check diagnostics. Such a request runs one read-only observation and provides its results to the local model for explanation. Informational questions (for example, "How do I run diagnostics?") do not start an observation. The Discord bot service credential does not gain this capability through chat.
GET /api/diagnostics/runbooks?platform=proxmox-ve&signal=proxmox.inventory.empty
Requires the viewer role. Returns bundled reviewed runbooks, optionally
filtered by platform and signal. limit is 1–100 (default 50).
GET /api/diagnostics/runbooks/{runbook_id} Requires the viewer role. Returns one bundled reviewed runbook or HTTP 404. Runbook steps are manual diagnostic guidance; the endpoint does not execute them.
GET /api/diagnostics/knowledge/search?query=inventory+403&platform=proxmox-ve
Requires the viewer role. Searches only bundled reviewed runbooks with
deterministic local lexical matching. query is required (1–160 characters);
limit is 1–25 (default 10). Historical incidents and drafts are not
searched. These read-only routes do not run observations or model inference.
AI Interface
The AI Interface is an admin-only server console with one persistent transcript shared among administrators. Turns are attributed to the signed-in admin. Private AI Chat conversations are stored separately and are never loaded into the shared console context. Do not enter private chat content, personal details, passwords, or secrets in the shared console.
GET /api/interface/conversation Returns the single shared transcript, with author attribution for user turns.
POST /api/interface/chat
Requires the admin role and a valid CSRF token/origin. Body:
{"message": "Check backend health"}
The local model may run a read-only diagnostic observation when explicitly requested. Other server commands, configuration changes, repairs, and maintenance actions are not implemented. The shared conversation is not a general Chat/Research workspace.
Chat Endpoints
POST /api/chat
Requires the operator role.
Body:
{
"message": "Hello",
"conversation_id": "optional conversation UUID"
}
The request does not accept a sampling-temperature override. The current
llama.cpp client sends a fixed temperature of 0.7.
Returns:
- AI response from the local llama.cpp server, generated by the loaded model.
- latency_ms: end-to-end generation request time.
- tokens: generated token count when reported by llama.cpp.
- When conversation_id is provided, stores this account's user/assistant
messages and includes bounded recent conversation turns in model context.
Returns HTTP 503 with a diagnostic message when the local llama.cpp server is not reachable or does not return a valid chat response.
All user-facing assistant replies are filtered server-side to remove
<think>...</think> reasoning blocks before JSON, WebSocket, or SSE content is
returned. The shared LLM client applies the same filter to standard and
streaming AI Chat, Research, and AI Interface responses. Discord uses
POST /api/chat and receives the filtered reply. Assistant messages are also
filtered before persistence, on transcript reads, and before reuse as model
context; user messages are left unchanged. An unclosed reasoning block is
dropped through the end of the response. No debug mode exposes raw reasoning;
any future reasoning visibility is reserved for an explicitly gated
developer/debug mode.
Conversation history belongs to the authenticated account (provider plus username). Requests made using the Discord bot service credential are not persisted. An invalid, deleted, or another account's conversation ID returns HTTP 404.
For admin sessions only, AI Chat, the AI Interface, and Research receive the signed-in admin's identity and the newest explicitly published shared insights as system context. Private transcripts remain account-scoped and are never used as another admin's context. Shared context is limited to 20 notes and 8,000 characters. This is retrieved prompt context, not model training. The Discord bot service credential and non-admin sessions receive no shared admin context.
GET /api/memory/conversations?limit=50 Requires the operator role. Lists up to 100 conversations belonging only to the current account, newest first, with title, timestamps, and message count.
POST /api/memory/conversations Requires the operator role. Creates an empty conversation and returns its UUID.
GET /api/memory/conversations/{conversation_id} Requires the operator role. Returns that account's full saved message history. Messages include role, content, creation time, and optional research source links.
DELETE /api/memory/conversations/{conversation_id} Requires the operator role. Permanently deletes that account's conversation and all its messages.
GET /api/memory/shared-insights Requires the admin role. Lists published operational notes shared across admins, with publisher identity and creation time.
POST /api/memory/shared-insights
Requires the admin role and a valid CSRF token/origin. Body:
{"content": "Operational note to share with admins"}
Publishes one explicitly approved note (1–2,000 characters). The store is limited to 100 notes; at capacity, the API returns HTTP 409 until an admin removes an outdated note. No transcript or chat message is implicitly published.
DELETE /api/memory/shared-insights/{insight_id} Requires the admin role and a valid CSRF token/origin. Removes a shared note; any admin may remove a published note. Returns HTTP 404 if it does not exist.
POST /api/research
Requires the operator role.
Body:
{
"message": "What changed in the latest Python release?"
}
Searches through the configured SearXNG instance, asks the local model to
summarize up to five results, and returns reply, sources,
search_latency_ms, latency_ms, and tokens. Each source includes title,
url, snippet, and page_status (fetched, excerpt_only, or
not_fetched). The backend attempts full-page retrieval for at most the first
three sources; the rest use their SearXNG excerpt. Fetches accept only public
HTTP(S) destinations on ports 80/443, resolve and pin public IP addresses,
revalidate every redirect (maximum three), require an HTML content type, and
bound each response to 1 MB and five seconds. Failed, unsafe, oversized, or
non-HTML fetches fall back to the search excerpt. Fetched page text and search
excerpts are explicitly untrusted evidence; instructions in them are never
followed. For admin sessions, explicitly published shared admin insights and
the current admin identity are included alongside the research safety prompt.
Returns HTTP 503 if SearXNG is unavailable or its search response is unusable;
model-stream errors are reported in-band by the streaming endpoint below.
Configure the SearXNG base URL with SEARXNG_BASE_URL (default
http://127.0.0.1:8888); its JSON output must be enabled in the SearXNG
configuration. Queries may be forwarded by SearXNG to its upstream search
engines; do not include secrets or private data.
POST /api/research/stream
Requires the operator role and a valid CSRF token/origin. Uses the same request
body and bounded page-retrieval policy as POST /api/research; the browser
uses this endpoint for AI Chat's Research Web mode. Returns Server-Sent Events
(text/event-stream):
event: sourceswith{"sources": [...], "search_latency_ms": 123}before answer generation. Each source includespage_status.event: tokenwith{"content": "..."}for each generated text chunk.event: donewith{"latency_ms": 1234}after the completed assistant turn and its citations have been persisted when a conversation ID was supplied.event: errorwith{"detail": "..."}if generation or persistence fails after streaming has begun. Partial replies are not saved as completed turns.
Search/preparation failures return HTTP 503 before the stream begins.
Authenticated conversation IDs use the same account-scoped history and admin
shared-context behavior as POST /api/research.
WS /ws/chat
Requires an authenticated operator session and an allowed dashboard origin.
Streams a local chat reply as llama.cpp generates it. The connection stays open
for multiple requests, one at a time. Browsers must connect from an origin in
AI_SERVER_CORS_ORIGINS; connections from other origins are rejected.
Client sends {"message": "Hello", "conversation_id": "optional UUID"} (1
to AI_SERVER_MAX_CHAT_CHARS characters, default 4000). When a conversation
ID is supplied, the backend persists the turn and uses up to the 20 most
recent saved messages, bounded to 12,000 characters, as prior model context.
Admin sessions also receive the signed-in admin identity and bounded,
explicitly published shared insights; private transcripts from other accounts
are not retrieved.
Server sends JSON messages:
- {"type": "token", "content": "..."} for each text chunk.
- {"type": "done", "latency_ms": 1234} when the reply is complete.
- {"type": "error", "detail": "..."} for invalid input (the connection stays
open) or when llama.cpp is unreachable or returns an invalid stream.
Closing the connection mid-reply stops generation. The dashboard uses this
endpoint for ordinary chat and falls back to POST /api/chat if the stream
cannot start. AI Chat's Research Web mode uses POST /api/research/stream.
Model Endpoints
GET /api/models
Requires the viewer role.
Discovers .gguf files in AI_SERVER_MODEL_DIR (or the platform default)
and returns each file's name, filename, format, size, and load status. The
response also includes active_model, runtime_status, runtime_error, and
model_directory. A model is marked loaded only when llama.cpp's /props
reports that exact file path.
POST /api/model/select
Requires the operator role.
Body:
{
"model_id": "qwen2.5-1.5b-instruct-q4_k_m.gguf"
}
Selection restarts the loopback
llama.cpp server with the requested installed model, waits for /props to
confirm that model is loaded, then returns:
{
"success": true,
"active_model": "qwen2.5-1.5b-instruct-q4_k_m.gguf",
"restarted": true
}
Selecting a model that is not installed returns HTTP 404. Unsafe or failed server restarts return HTTP 503; if loading the requested model fails, the backend attempts to restore the previously active model and reports that recovery outcome in the error detail.
This is the current single-model selection API. It does not expose Utility, Household, or Maintenance roles, multiple active model servers, or per-request role routing. The proposed resident Utility/profile architecture remains unimplemented.
Virtual Machines and Network Services
GET /api/proxmox/vms
Requires an authenticated dashboard session with the viewer role or higher.
Reads a filtered VM/container inventory from the configured Proxmox API with
the backend-only PROXMOX_INVENTORY_TOKEN_ID and
PROXMOX_INVENTORY_TOKEN_SECRET. Proxmox credentials are never sent to the
browser.
When the provider is not configured, returns HTTP 200 with explicit state:
{
"configured": false,
"status": "not_configured",
"guests": [],
"count": 0,
"observed_at": null
}
When connected, returns configured: true, status: "connected", a guests
array containing only name, vmid, node, type, and status, the guest
count, and a UTC observed_at timestamp. Upstream failures return HTTP 503;
the endpoint does not substitute cached or sample inventory. The provider rejects redirects, verifies TLS certificates, and makes only a
read-only cluster-resource request. For the nested Proxmox lab only, Python
may reject the Proxmox-generated CA because it lacks the X.509 keyUsage
extension. The explicit PROXMOX_API_ALLOW_MISSING_CA_KEY_USAGE compatibility
setting relaxes OpenSSL's strict X.509 extension-policy checks but does not
disable configured CA-chain, certificate-validity, or hostname verification.
Do not use it for physical/production deployment.
The portable read-only and proposal-only contracts for VM, network-service, and game-server integrations are documented in infrastructure-contracts.md. Proposal contracts are not HTTP routes and have no execution provider.
The VM dashboard refreshes the inventory when opened and on explicit refresh. It has no create, power, snapshot, configuration, or console operations. DHCPv4, DHCPv6, DNS, web-hosting, and routing/firewall remain planning-only; no network-service monitoring or management API is implemented. When implemented, the initial network-service integration is intended to be observe-only and may report services hosted inside VMs; VM inventory by itself does not verify service health. It must not expose mutation endpoints or automatic remediation in that initial scope. Any later action-capable API requires a separately reviewed and explicitly authorized autonomy phase.
Discord Bot Endpoints
GET /api/bot/status
Requires the viewer role for dashboard sessions. The Discord bot's machine
credential may also call this route.
Returns:
- Online/offline
- Latency
- Active model
GET /api/bot/logs
Requires the admin role for dashboard sessions.
Returns:
- Recent bot log lines
POST /api/bot/heartbeat
POST /api/bot/logs
Discord-bot-only machine routes; both require the configured AI_SERVER_BOT_API_TOKEN service credential. The heartbeat reports bot latency/commands and returns any pending restart request. The log endpoint records a short, content-free operational event. These routes reject dashboard session credentials.
POST /api/bot/restart Requires the admin role and CSRF protection. Requests a bot restart and returns HTTP 409 when the bot is offline.
Vision / Embedding (Stub)
GET /api/vision Returns the current vision-analysis stub response; viewer role required.
POST /api/vision/run Accepts image bytes and returns the current image-model stub response; operator role required.
POST /api/embed Requires the operator role or scoped Discord-bot credential. Returns the current embedding stub response.
POST /api/llm Operator-only alias for the local chat endpoint.