Sapphire runs a single FastAPI server on port 8073 (HTTPS). Every endpoint below requires authentication — either a browser session or an API key.
Routes are split across modules under core/routes/, plus a few app-level routes in core/api_fastapi.py (login/setup pages, avatar files, plugin web assets). This doc covers the full surface.
Authentication
Browser Session
Log in at /login with your password. Sessions last 30 days.
Named API Tokens (Programmatic Access — preferred)
For scripts, external tools, and integrations, mint a named token at Settings > System > API Keys (or POST /api/system/api-tokens) and send it as a Bearer header:
curl -k https://localhost:8073/api/status \
-H "Authorization: Bearer $SAPPHIRE_TOKEN"
The full token value is shown once at creation — copy it then. Listing shows only the last 4 characters; each token is individually revocable (DELETE /api/system/api-tokens/{token_id}). Prefer this over X-API-Key: named, revocable per caller, and it never exposes your password hash.
X-API-Key (legacy)
Older scripts and internal tools may still send the bcrypt password hash as a header:
curl -k https://localhost:8073/api/status \
-H "X-API-Key: $(cat ~/.config/sapphire/secret_key)"
The key is the bcrypt hash stored in your config directory:
| OS |
Path |
| Linux |
~/.config/sapphire/secret_key |
| macOS |
~/Library/Application Support/Sapphire/secret_key |
| Windows |
%APPDATA%\Sapphire\secret_key |
This file is created during initial setup. To reset, delete it and restart Sapphire.
CSRF
CSRF tokens are required for browser sessions on POST/PUT/DELETE requests. Bearer-token and X-API-Key auth bypass CSRF — no extra headers needed. The /ws/conversation WebSocket accepts session cookies only (same-origin enforced).
Rate Limiting
5 attempts per 60 seconds per IP on auth endpoints.
Endpoints
Core
| Method |
Endpoint |
Purpose |
| GET |
/api/health |
Health check |
| GET |
/api/status |
Unified UI state (prompt, context, spice, TTS/STT readiness) |
| GET |
/api/init |
Mega initialization (all toolsets, prompts, personas, spices, settings) |
Chat
| Method |
Endpoint |
Purpose |
| POST |
/api/chat |
Send message, get response |
| POST |
/api/chat/stream |
Streaming SSE response — one turn per chat: a second stream while a turn is live on that chat returns 409. Optional chat in the body addresses a chat BY NAME (a room's rail bound to its session): the active chat runs as before; any other chat runs pinned to its own stored settings, never the active pointer. Sealed → 409, missing → 404. continue_from (an assistant turn's timestamp) resumes the chat's last reply in place: no user row is sent or saved, the row is edited on success and untouched on Stop |
| POST |
/api/chat/attach |
Rejoin the live turn on a chat after losing the feed ({chat?, since?}). Turns are server-owned: the engine runs on its own thread and a viewer leaving never ends it. Replays the turn's ring exact-once after since (every SSE line carries seq), then follows live in the same wire format — text only, audio is never replayed. 204 = no live turn on that chat; 404 unknown chat; 409 sealed chat |
| POST |
/api/cancel |
Cancel active stream (?chat= scopes to one chat) |
| GET |
/api/events |
SSE event stream (real-time UI updates) |
| GET |
/api/history |
Get chat message history (?chat= reads a chat by name; absent = active) |
Chat Sessions
| Method |
Endpoint |
Purpose |
| GET |
/api/chats |
List all chats |
| POST |
/api/chats |
Create new chat |
| GET |
/api/chats/search |
Search chat content (private chats excluded while the vault is sealed) |
| DELETE |
/api/chats/{name} |
Delete chat (also deletes its plugin/playthrough data) |
| POST |
/api/chats/{name}/activate |
Switch active chat |
| GET |
/api/chats/active |
Get active chat name |
| GET |
/api/chats/{name}/settings |
Get chat settings |
| PUT |
/api/chats/{name}/settings |
Update chat settings |
| POST |
/api/chats/{name}/rename |
Rename a chat (carries tool images, message rows, and RAG scope along) |
| POST |
/api/chats/{name}/archive |
Toggle a chat's archived flag (UI shade — chat stays fully functional) |
| GET |
/api/chats/{name}/export |
Full raw export of one chat (name, settings, messages) |
| POST |
/api/chats/bulk-export |
Export many chats as one JSON document |
| POST |
/api/chats/bulk-export-zip |
Export many chats as a zip, one JSON file per chat |
| POST |
/api/chats/bulk-delete |
Delete many chats in one call (per-chat results) |
| POST |
/api/chats/bulk-clear |
Clear messages in many chats (chats survive, histories wiped) |
| POST |
/api/chats/{name}/trim |
Turn-snapped middle trim — keep the first A and last B turns |
| POST |
/api/chats/{name}/repair |
Diagnose/repair a chat's unreadable message rows |
| POST |
/api/chats/{name}/compress |
Start the background compress job (summarize history) |
| GET |
/api/chats/compress/status |
Status of the one-at-a-time compress job (UI polls this) |
| GET |
/api/chats/{name}/prompt-preview |
The exact system prompt + ghost envelope the next turn would send |
Message History
| Method |
Endpoint |
Purpose |
| DELETE |
/api/history/messages |
Remove messages (by count, user message, or clear all with count=-1) |
| POST |
/api/history/messages/remove-last-assistant |
Remove last assistant message |
| POST |
/api/history/messages/remove-from-assistant |
Remove from last assistant message onward |
| DELETE |
/api/history/tool-call/{id} |
Delete specific tool call |
| POST |
/api/history/messages/edit |
Edit a message (the message-edit routes act on the active chat; a rail bound by name sends ?chat= and gets 409 if that chat is no longer active) |
| GET |
/api/history/raw |
Export raw chat history |
| POST |
/api/history/import |
Import chat history |
TTS / STT / Audio
| Method |
Endpoint |
Purpose |
| POST |
/api/tts |
Generate TTS audio |
| POST |
/api/tts/stream |
Streaming TTS — per-chunk OGG over chunked transfer (v2.7.0) |
| POST |
/api/tts/preview |
Preview voice sample |
| GET |
/api/tts/status |
TTS server status |
| POST |
/api/tts/stop |
Stop TTS playback |
| POST |
/api/tts/test |
Test TTS provider connectivity |
| GET |
/api/tts/voices |
List voices for active TTS provider |
| POST |
/api/tts/voices |
List voices (with optional api_key for pre-save browsing) |
| POST |
/api/transcribe |
Transcribe audio file |
| GET |
/api/stt/vad-status |
Silero VAD warmup status |
| POST |
/api/stt/vad-test |
Test mic input against the VAD threshold |
| POST |
/api/mic/active |
Set web mic active state (suppresses wakeword) |
| POST |
/api/upload/image |
Upload image for chat |
| GET |
/api/audio/devices |
List audio devices |
| POST |
/api/audio/test-input |
Test input device |
| POST |
/api/audio/test-output |
Test output device |
Settings
| Method |
Endpoint |
Purpose |
| GET |
/api/settings |
Get all settings |
| GET |
/api/settings/{key} |
Get a single setting |
| PUT |
/api/settings/{key} |
Update a single setting |
| DELETE |
/api/settings/{key} |
Reset a setting to default |
| PUT |
/api/settings/batch |
Batch update multiple settings |
| POST |
/api/settings/reload |
Force reload from disk |
| POST |
/api/settings/reset |
Reset all settings to defaults |
| GET |
/api/settings/help |
Get setting descriptions |
| GET |
/api/settings/help/{key} |
Get help for a specific setting |
| GET |
/api/settings/tiers |
Get hot vs restart-required status |
| GET |
/api/settings/tool-settings |
Get tool-specific settings |
| GET |
/api/settings/chat-defaults |
Get chat default settings |
| PUT |
/api/settings/chat-defaults |
Update chat defaults |
| DELETE |
/api/settings/chat-defaults |
Reset chat defaults to factory |
| GET |
/api/settings/wakeword-models |
List available wakeword models |
Credentials & SOCKS Proxy
| Method |
Endpoint |
Purpose |
| GET |
/api/credentials |
List configured credential keys |
| PUT |
/api/credentials/llm/{provider} |
Set LLM API key |
| DELETE |
/api/credentials/llm/{provider} |
Remove LLM API key |
| GET |
/api/credentials/socks |
Get SOCKS proxy config |
| PUT |
/api/credentials/socks |
Set SOCKS proxy config |
| DELETE |
/api/credentials/socks |
Remove SOCKS proxy config |
| POST |
/api/credentials/socks/test |
Test SOCKS proxy connection |
| GET |
/api/socks/status |
Trust-strip truth: what the proxy env actually covers right now |
LLM Providers
| Method |
Endpoint |
Purpose |
| GET |
/api/llm/providers |
List LLM providers |
| PUT |
/api/llm/providers/{key} |
Update provider config |
| PUT |
/api/llm/fallback-order |
Set LLM fallback order |
| POST |
/api/llm/test/{provider} |
Test LLM connection |
| POST |
/api/llm/test-thinking/{provider} |
Probe whether the provider's 'disable thinking' switch actually works |
| POST |
/api/llm/custom-providers |
Add a custom LLM provider |
| DELETE |
/api/llm/custom-providers/{key} |
Remove a custom LLM provider |
| GET |
/api/llm/custom-providers/{key}/models |
Fetch models from a custom provider |
| GET |
/api/llm/presets |
List LLM provider presets |
Provider Registry (TTS / STT)
| Method |
Endpoint |
Purpose |
| GET |
/api/tts/providers |
List TTS providers (core + plugin) |
| GET |
/api/stt/providers |
List STT providers (core + plugin) |
Embeddings
| Method |
Endpoint |
Purpose |
| POST |
/api/embedding/test |
Test embedding provider |
| GET |
/api/embedding/providers |
List available embedding providers |
| GET |
/api/embedding/integrity |
Check embedding dimension/integrity across stores |
| POST |
/api/embedding/reembed |
Re-embed all stored vectors (after provider/model change) |
| GET |
/api/embedding/reembed/status |
Re-embed progress |
| POST |
/api/embedding/reembed/cancel |
Cancel an in-progress re-embed |
Privacy Vault
Vault state ({exists, unlocked}) rides the top-level vault key on GET /api/status. Wrong passphrase returns 403, never 401. See PRIVACY.md.
| Method |
Endpoint |
Purpose |
| POST |
/api/vault/setup |
Create the vault and unlock it (409 if one exists) |
| POST |
/api/vault/unlock |
Unlock with the passphrase (idempotent) |
| POST |
/api/vault/lock |
Lock now — synchronous; evicts a private active chat first |
| POST |
/api/vault/rekey |
Change the passphrase (needs the current one; lock state preserved) |
| POST |
/api/vault/move |
Move a prompt or piece in/out of the vault |
| POST |
/api/vault/move-batch |
Move many at once ({direction, items:[{kind:'prompt',name}|{kind:'piece',comp_type,key}]}) — one request, per-item receipts, one change event; 409 if the vault is locked |
Chat privacy is per-chat: PUT /api/chats/{name}/settings with private_chat. Membership changes need the vault unlocked (403 otherwise); while sealed, private chats answer as nonexistent on every by-name route.
System Prompt
| Method |
Endpoint |
Purpose |
| GET |
/api/system/status |
System status (detailed) |
| GET |
/api/system/prompt |
Get current system prompt |
| POST |
/api/system/merge-updates |
Merge missing prompts + personas from app updates |
Personas
| Method |
Endpoint |
Purpose |
| GET |
/api/personas |
List all personas |
| GET |
/api/personas/{name} |
Get persona details |
| POST |
/api/personas |
Create persona |
| PUT |
/api/personas/{name} |
Update persona |
| DELETE |
/api/personas/{name} |
Delete persona |
| POST |
/api/personas/{name}/duplicate |
Clone persona |
| POST |
/api/personas/{name}/load |
Activate persona on current chat |
| POST |
/api/personas/from-chat |
Create persona from current chat settings |
| POST |
/api/personas/{name}/avatar |
Upload avatar (max 4MB) |
| DELETE |
/api/personas/{name}/avatar |
Remove avatar |
| GET |
/api/personas/{name}/avatar |
Get avatar image |
| PUT |
/api/personas/default |
Set default persona for new chats |
| DELETE |
/api/personas/default |
Clear default persona |
| GET |
/api/personas/{name}/export.png |
Export persona as a PNG character card (avatar image + full bundle in a sapphire_persona tEXt chunk) |
| POST |
/api/personas/import-card |
Import persona from an uploaded PNG character card (multipart; overwrite_prompt/overwrite_avatar/overwrite_persona flags; JSON bundles go through /api/personas/import) |
| POST |
/api/personas/import |
Import persona from a portable JSON bundle (legacy format) |
Prompts
| Method |
Endpoint |
Purpose |
| GET |
/api/prompts |
List prompts (pack entries with kind != user are omitted; a hidden {name: kind} map rides along for dropdown labels) |
| GET |
/api/prompts/{name} |
Get prompt details |
| PUT |
/api/prompts/{name} |
Create or update prompt |
| DELETE |
/api/prompts/{name} |
Delete prompt |
| POST |
/api/prompts/{name}/load |
Activate prompt on current chat |
| POST |
/api/prompts/reload |
Reload from disk |
| POST |
/api/prompts/reset |
Reset to defaults |
| POST |
/api/prompts/merge |
Merge defaults into current |
| POST |
/api/prompts/reset-chat-defaults |
Reset chat defaults to factory |
| GET |
/api/prompts/components |
List prompt components (hidden pack scaffolding omitted; hidden_keys carries their names for missing-ref checks) |
| PUT |
/api/prompts/components/{type}/{key} |
Save prompt component |
| DELETE |
/api/prompts/components/{type}/{key} |
Delete prompt component |
| GET |
/api/prompts/piece-usage |
Usage index: which prompts use each piece (+ dangling refs, vault flags) |
| POST |
/api/prompts/pieces/rename |
Safe rename: moves a piece in its store and repoints every reference |
| POST |
/api/prompts/pieces/strip-danglers |
Strip all references to missing pieces (409 while vault locked) |
| GET |
/api/prompts/pieces/trash |
List trashed pieces (plaintext + unlocked-vault stores) |
| POST |
/api/prompts/pieces/trash |
Soft-delete pieces into the trash |
| POST |
/api/prompts/pieces/trash/restore |
Restore trashed pieces (never overwrites a live key) |
| POST |
/api/prompts/pieces/trash/purge |
Empty the piece trash (both stores while unlocked) |
| Method |
Endpoint |
Purpose |
| GET |
/api/toolsets |
List toolsets (?filter=sidebar to exclude module-level) |
| GET |
/api/toolsets/current |
Get active toolset |
| POST |
/api/toolsets/{name}/activate |
Activate toolset |
| POST |
/api/toolsets/custom |
Save custom toolset |
| DELETE |
/api/toolsets/{name} |
Delete toolset |
| POST |
/api/toolsets/{name}/emoji |
Set toolset emoji |
| GET |
/api/functions |
List all available functions |
| POST |
/api/functions/enable |
Enable specific functions |
Spices
| Method |
Endpoint |
Purpose |
| GET |
/api/spices |
List all spices |
| POST |
/api/spices |
Add a new spice to a category |
| PUT |
/api/spices/{category}/{index} |
Update a spice |
| DELETE |
/api/spices/{category}/{index} |
Delete a spice |
| POST |
/api/spices/category |
Create spice category |
| PUT |
/api/spices/category/{name} |
Rename spice category |
| DELETE |
/api/spices/category/{name} |
Delete spice category |
| POST |
/api/spices/category/{name}/emoji |
Set category emoji |
| POST |
/api/spices/category/{name}/toggle |
Enable/disable category |
| POST |
/api/spices/reload |
Reload spices from disk |
Spice Sets
| Method |
Endpoint |
Purpose |
| GET |
/api/spice-sets |
List spice sets |
| GET |
/api/spice-sets/current |
Get active spice set |
| POST |
/api/spice-sets/{name}/activate |
Activate spice set |
| POST |
/api/spice-sets/custom |
Save custom spice set |
| DELETE |
/api/spice-sets/{name} |
Delete spice set |
| POST |
/api/spice-sets/{name}/emoji |
Set spice set emoji |
Memory
| Method |
Endpoint |
Purpose |
| GET |
/api/memory/scopes |
List memory scopes |
| POST |
/api/memory/scopes |
Create scope |
| DELETE |
/api/memory/scopes/{name} |
Delete scope |
| GET |
/api/memory/list |
List memories (grouped by label) |
| PUT |
/api/memory/{id} |
Update memory |
| DELETE |
/api/memory/{id} |
Delete memory |
| GET |
/api/memory/export |
Export all memories in scope as JSON |
| POST |
/api/memory/import |
Import memories from JSON |
| GET |
/api/memory/duplicates |
Find near-duplicate memories via vector similarity |
Knowledge
| Method |
Endpoint |
Purpose |
| GET |
/api/knowledge/scopes |
List knowledge scopes |
| POST |
/api/knowledge/scopes |
Create scope |
| DELETE |
/api/knowledge/scopes/{name} |
Delete scope |
| GET |
/api/knowledge/tabs |
List knowledge tabs (in scope) |
| POST |
/api/knowledge/tabs |
Create tab |
| GET |
/api/knowledge/tabs/{id} |
Get tab with entries |
| PUT |
/api/knowledge/tabs/{id} |
Update tab |
| DELETE |
/api/knowledge/tabs/{id} |
Delete tab |
| POST |
/api/knowledge/tabs/{id}/entries |
Add entry |
| POST |
/api/knowledge/tabs/{id}/upload |
Upload file (auto-chunks + embeds) |
| DELETE |
/api/knowledge/tabs/{id}/file/{name} |
Delete uploaded file entries |
| PUT |
/api/knowledge/entries/{id} |
Update entry |
| DELETE |
/api/knowledge/entries/{id} |
Delete entry |
| GET |
/api/knowledge/tabs/{id}/export |
Export knowledge tab as JSON |
| POST |
/api/knowledge/tabs/import |
Import knowledge tab from JSON |
| GET |
/api/knowledge/dedup |
Find near-duplicate knowledge entries |
| DELETE |
/api/knowledge/dedup/resolve |
Resolve/remove a duplicate entry |
People
| Method |
Endpoint |
Purpose |
| GET |
/api/knowledge/people/scopes |
List people scopes |
| POST |
/api/knowledge/people/scopes |
Create scope |
| DELETE |
/api/knowledge/people/scopes/{name} |
Delete scope |
| GET |
/api/knowledge/people |
List people (in scope) |
| POST |
/api/knowledge/people |
Create/update person |
| DELETE |
/api/knowledge/people/{id} |
Delete person |
| POST |
/api/knowledge/people/import-vcf |
Import vCard file |
| GET |
/api/knowledge/people/export |
Export people as JSON |
| POST |
/api/knowledge/people/import |
Import people from JSON |
Goals
| Method |
Endpoint |
Purpose |
| GET |
/api/goals/scopes |
List goal scopes |
| POST |
/api/goals/scopes |
Create scope |
| DELETE |
/api/goals/scopes/{name} |
Delete scope |
| GET |
/api/goals |
List goals (filtered by scope/status) |
| GET |
/api/goals/{id} |
Get a single goal |
| POST |
/api/goals |
Create goal |
| PUT |
/api/goals/{id} |
Update goal |
| POST |
/api/goals/{id}/progress |
Add progress note |
| DELETE |
/api/goals/{id} |
Delete goal |
Per-Chat Documents (RAG)
| Method |
Endpoint |
Purpose |
| POST |
/api/chats/{name}/documents |
Upload document to chat |
| GET |
/api/chats/{name}/documents |
List chat documents |
| DELETE |
/api/chats/{name}/documents/{file} |
Remove document |
Heartbeat (Scheduled Tasks)
| Method |
Endpoint |
Purpose |
| GET |
/api/continuity/tasks |
List scheduled tasks |
| POST |
/api/continuity/tasks |
Create task |
| GET |
/api/continuity/tasks/{id} |
Get task |
| PUT |
/api/continuity/tasks/{id} |
Update task |
| DELETE |
/api/continuity/tasks/{id} |
Delete task |
| POST |
/api/continuity/tasks/{id}/run |
Run task now |
| POST |
/api/continuity/tasks/{id}/cancel |
Stop a task's in-flight run (the ⏹ on a running card). Ends at the next cancel point — between LLM rounds — and drops queued fires behind it; the task stays enabled. Toggling a task off cancels implicitly |
| GET |
/api/continuity/status |
Scheduler status |
| GET |
/api/continuity/activity |
Recent activity log |
| GET |
/api/continuity/timeline |
Upcoming schedule (future only) |
| GET |
/api/continuity/merged-timeline |
Past activity + future schedule with NOW marker |
Daemon Events
| Method |
Endpoint |
Purpose |
| GET |
/api/events/sources |
List daemon event sources from loaded plugins |
| POST |
/api/events/emit/{source} |
Emit a daemon event to trigger matching tasks |
Backup
| Method |
Endpoint |
Purpose |
| GET |
/api/backup/list |
List backups |
| POST |
/api/backup/create |
Create backup |
| DELETE |
/api/backup/delete/{name} |
Delete backup |
| GET |
/api/backup/download/{name} |
Download backup archive (tar.gz) |
| GET |
/api/backup/health |
Backup trust at a glance: scheduler liveness, newest backup age, sentinel halt |
| POST |
/api/backup/estimate |
Estimate backup size with the given exclude patterns (per-folder breakdown) |
| POST |
/api/backup/restore |
Restore an existing backup over user/ (validates, decrypts if needed, restarts) |
| POST |
/api/backup/restore-upload |
Restore from an uploaded backup file |
| GET |
/api/backup/restore-result |
Outcome of the last restore, for the post-reboot banner |
| DELETE |
/api/backup/restore-result |
Dismiss the restore-result banner |
Agents
| Method |
Endpoint |
Purpose |
| GET |
/api/agents/status |
List agents (?chat=name to filter) |
| GET |
/api/agents/providers |
List available LLM providers for agents |
| POST |
/api/agents/{id}/dismiss |
Dismiss an agent |
Workspace Runner
| Method |
Endpoint |
Purpose |
| POST |
/api/workspace/run |
Run a command in a workspace project |
| POST |
/api/workspace/stop |
Stop a running workspace process |
| GET |
/api/workspace/status |
Get status of all running workspaces |
Plugins — Listing & Lifecycle
| Method |
Endpoint |
Purpose |
| GET |
/api/webui/plugins |
List all plugins (loaded, enabled, manifest info) |
| PUT |
/api/webui/plugins/toggle/{name} |
Enable/disable a plugin (live load/unload) |
| POST |
/api/plugins/rescan |
Discover newly added plugins without restart |
| POST |
/api/plugins/{name}/reload |
Hot-reload a plugin (unload + load) |
| GET |
/api/plugins/{name}/check-deps |
Check a plugin's pip dependencies |
| POST |
/api/plugins/{name}/install-deps |
Install a plugin's declared pip dependencies |
| PUT |
/api/plugins/{name}/surfaces |
Set where the plugin's presence injections show up |
| POST |
/api/plugins/{name}/build-env |
Build (or rebuild) a plugin's dedicated conda env in the background |
| GET |
/api/plugins/{name}/env-status |
Env build/readiness status + build log tail (UI polls this) |
| DELETE |
/api/plugins/{name}/env |
Remove a plugin's conda env (services stop; rebuild any time) |
Plugins — Install & Uninstall
| Method |
Endpoint |
Purpose |
| POST |
/api/plugins/install |
Install plugin from GitHub URL or zip upload |
| DELETE |
/api/plugins/{name}/uninstall |
Uninstall user plugin (unload + delete) |
| GET |
/api/plugins/{name}/check-update |
Check for updates from install source |
| POST |
/api/plugins/{name}/revert |
Swap a plugin back to the version retained by its last update |
Store (read-only proxy of sapphireblue.dev catalog)
| Method |
Endpoint |
Purpose |
| GET |
/api/store/status |
Store reachability + cache info |
| GET |
/api/store/categories |
List plugin categories with counts |
| GET |
/api/store/plugins/list |
List/search store plugins (q, category, featured, sort, page, per_page) |
| GET |
/api/store/plugins/{slug} |
Single plugin detail (description, screenshots, version, author) |
| GET |
/api/store/personas/list |
List/search store personas (same query params as plugins) |
| GET |
/api/store/personas/categories |
Persona categories with counts |
| GET |
/api/store/personas/{slug} |
Detail page for one persona |
| POST |
/api/store/personas/{slug}/install |
Download a persona's PNG card and import it (same overwrite flags as import-card) |
Dashboard
| Method |
Endpoint |
Purpose |
| GET |
/api/dashboard/system-info |
Mem usage, thread count, uptime, disk stats, display name |
| GET |
/api/dashboard/component-status |
TTS/STT/wakeword/LLM readiness states |
| GET |
/api/dashboard/widgets |
List active widget panels for the user's dashboard |
| PUT |
/api/dashboard/widgets |
Save the user's panel layout (order, sizes, settings) |
| GET |
/api/dashboard/widgets/available |
List widgets registered by enabled plugins |
Plugin Settings
| Method |
Endpoint |
Purpose |
| GET |
/api/webui/plugins/{name}/settings |
Get plugin settings |
| PUT |
/api/webui/plugins/{name}/settings |
Save plugin settings |
| DELETE |
/api/webui/plugins/{name}/settings |
Reset plugin settings |
| GET |
/api/webui/plugins/config |
Get plugin config metadata |
Apps, Games & Themes
| Method |
Endpoint |
Purpose |
| GET |
/api/apps |
List plugin apps (plugins with an app/ directory) |
| GET |
/api/games |
List games registered via capabilities.games (empty unless game plugins are enabled) |
| GET |
/api/themes |
List all themes (core + plugin manifest themes) |
| GET |
/api/motions |
List motion themes (animation layer) |
Scene Backgrounds
| Method |
Endpoint |
Purpose |
| GET |
/api/backgrounds |
List the scene library ([{name, url, thumb}]) |
| GET |
/api/backgrounds/{name} |
Serve a scene image (?thumb=1 for thumbnail) |
| POST |
/api/backgrounds |
Upload a scene (re-encoded to webp, full + thumb) |
| DELETE |
/api/backgrounds/{name} |
Delete a scene (full + thumb) |
Fonts
| Method |
Endpoint |
Purpose |
| GET |
/api/fonts |
Font registry + downloaded state (Visual tab's Type cards) |
| GET |
/api/fonts/file/{family} |
Serve a downloaded font file |
| POST |
/api/fonts/download |
Fetch a pinned font (SOCKS-aware), verify sha256, install atomically |
Video Guide
| Method |
Endpoint |
Purpose |
| GET |
/api/videos |
Multi-channel video feed for the in-app Video Guide (cached; ?refresh=1 re-fetches) |
Home Assistant Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/webui/plugins/homeassistant/defaults |
Get HA default settings |
| POST |
/api/webui/plugins/homeassistant/test-connection |
Test HA connection |
| POST |
/api/webui/plugins/homeassistant/test-notify |
Test HA notification |
| PUT |
/api/webui/plugins/homeassistant/token |
Save HA token |
| GET |
/api/webui/plugins/homeassistant/token |
HA token status |
| POST |
/api/webui/plugins/homeassistant/entities |
Fetch HA entities |
Email Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/webui/plugins/email/credentials |
Get email credentials |
| PUT |
/api/webui/plugins/email/credentials |
Save email credentials |
| DELETE |
/api/webui/plugins/email/credentials |
Remove email credentials |
| POST |
/api/webui/plugins/email/test |
Test email connection |
| GET |
/api/email/accounts |
List email accounts (multi-scope) |
| PUT |
/api/email/accounts/{scope} |
Set email account for scope |
| DELETE |
/api/email/accounts/{scope} |
Remove email account for scope |
| POST |
/api/email/accounts/{scope}/test |
Test email account |
GitHub Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/github/accounts |
List GitHub accounts (multi-scope) |
| PUT |
/api/github/accounts/{scope} |
Set GitHub account (PAT) for scope |
| DELETE |
/api/github/accounts/{scope} |
Remove GitHub account for scope |
Bitcoin Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/bitcoin/wallets |
List bitcoin wallets (multi-scope) |
| PUT |
/api/bitcoin/wallets/{scope} |
Set wallet for scope |
| DELETE |
/api/bitcoin/wallets/{scope} |
Remove wallet for scope |
| POST |
/api/bitcoin/wallets/{scope}/check |
Check wallet balance |
| GET |
/api/bitcoin/wallets/{scope}/export |
Export wallet details |
Google Calendar Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/gcal/accounts |
List Google Calendar accounts (multi-scope) |
| PUT |
/api/gcal/accounts/{scope} |
Set GCal account for scope |
| DELETE |
/api/gcal/accounts/{scope} |
Remove GCal account for scope |
SSH Plugin
| Method |
Endpoint |
Purpose |
| GET |
/api/webui/plugins/ssh/servers |
Get configured SSH servers |
| PUT |
/api/webui/plugins/ssh/servers |
Replace SSH servers list |
| POST |
/api/webui/plugins/ssh/test |
Test SSH connection |
Avatars
| Method |
Endpoint |
Purpose |
| GET |
/api/avatars |
Get avatar paths for user/assistant |
| POST |
/api/avatar/upload |
Upload avatar (max 4MB) |
| GET |
/api/avatar/check/{role} |
Check if avatar exists for role |
| GET |
/api/avatar/{filename} |
Serve avatar file |
Body (Multi-Body Runtime)
| Method |
Endpoint |
Purpose |
| POST |
/api/body/wake |
Trigger a wake event on a registered body |
| GET |
/api/body/health |
Body runtime health/status |
| GET |
/api/body/events |
SSE stream of body/avatar events |
Conversation Mode
| Method |
Endpoint |
Purpose |
| WS |
/ws/conversation |
Browser conversation-mode WebSocket — connecting starts the mode, disconnecting ends it (session-cookie auth only) |
| GET |
/api/runtime/true-speech |
Current true-speech (conversation) mode state — ephemeral, for UI load-state |
| PUT |
/api/runtime/true-speech |
Enter/exit true-speech mode (continuous listen, no wakeword) |
Setup Wizard
| Method |
Endpoint |
Purpose |
| GET |
/api/setup/provider-status |
Check STT/TTS provider readiness |
| GET |
/api/setup/check-packages |
Check optional package installation status |
| GET |
/api/setup/wizard-step |
Get current wizard step |
| PUT |
/api/setup/wizard-step |
Set wizard step |
Metrics (Token Usage)
| Method |
Endpoint |
Purpose |
| GET |
/api/metrics/enabled |
Check if metrics tracking is enabled |
| PUT |
/api/metrics/enabled |
Toggle metrics tracking |
| GET |
/api/metrics/summary |
Aggregate token usage summary (?days=30) |
| GET |
/api/metrics/breakdown |
Usage broken down by model (?days=30) |
| GET |
/api/metrics/daily |
Daily usage for charting (?days=30) |
System Updates & Integrity
| Method |
Endpoint |
Purpose |
| GET |
/api/system/update-check |
Check for Sapphire updates |
| POST |
/api/system/update |
Apply update |
| DELETE |
/api/system/update |
Cancel a scheduled update that hasn't applied yet |
| GET |
/api/system/last-update-result |
Result of the most recent deferred update attempt (read-once, then cleared) |
| GET |
/api/system/integrity |
Verify the core install against the shipped manifest (SHA256) |
| POST |
/api/system/integrity/repair |
Restore files that don't match the manifest (git installs), per-file status |
| POST |
/api/system/restart |
Restart Sapphire |
| POST |
/api/system/shutdown |
Shutdown Sapphire |
API Tokens
| Method |
Endpoint |
Purpose |
| GET |
/api/system/api-tokens |
List programmatic API tokens |
| POST |
/api/system/api-tokens |
Create a named API token |
| DELETE |
/api/system/api-tokens/{token_id} |
Revoke an API token |
| POST |
/api/system/password |
Change the login password ({current, new}; verifies the current one, 5/min, 10+ chars and ≤72 bytes). The calling tab stays logged in; every other session is logged out. Rotates the legacy X-API-Key hash — named bearer tokens are unaffected |
| Method |
Endpoint |
Purpose |
| GET |
/api/tool-image/{id} |
Serve tool-generated image |
Docs
| Method |
Endpoint |
Purpose |
| GET |
/api/docs |
List documentation tree |
| GET |
/api/docs/search |
Search across all docs (?q=query) |
| GET |
/api/docs/{path} |
Get raw markdown content of a doc |
Static Assets & Plugin Web
| Method |
Endpoint |
Purpose |
| GET |
/plugin-web/{name}/{path} |
Serve plugin web/app assets |
| GET |
/workspace/{project}/{path} |
Serve Claude Code workspace files |
| GET |
/cdn-cache/{path} |
Serve locally cached third-party assets |
Reference for AI
Sapphire API reference for programmatic access. Single FastAPI server, https://localhost:8073; every endpoint is auth-gated.
AUTH:
- Browser: session cookie via /login (30-day sessions; CSRF token required on mutations)
- Programmatic (preferred):
Authorization: Bearer <token> — named API tokens minted at Settings > System > API Keys or POST /api/system/api-tokens (full value shown ONCE at creation; list shows last-4; revoke via DELETE /api/system/api-tokens/{token_id})
- Programmatic (legacy):
X-API-Key header carrying the bcrypt password hash from the secret_key file
- Bearer and X-API-Key both bypass CSRF
- /ws/conversation accepts session-cookie auth only (same-origin enforced)
- Rate limit: 5 attempts/60s per IP on auth endpoints
ROUTE MODULES (core/routes/):
- chat.py: chat + stream (SSE; one turn per chat — 409 if a turn is already live), cancel, events SSE, health/status/init, history editing, chat sessions + lifecycle (rename, archive, trim, repair, compress + status, bulk delete/clear/export, single export, prompt-preview)
- content.py: prompts, prompt components, piece trash/rename/usage, toolsets, functions, spices, spice sets, personas (PNG character-card export.png / import-card, legacy JSON import)
- settings.py: settings CRUD, credentials, SOCKS proxy + /api/socks/status, LLM providers + test + test-thinking, custom providers, presets, TTS/STT/embedding provider registries, system status, system prompt (read-only)
- system.py: backup suite (list/create/delete/download tar.gz/health/estimate/restore/restore-upload/restore-result), audio devices, continuity tasks, setup wizard, avatars, restart/shutdown, update + cancel + last-update-result, integrity verify + repair, metrics, api-tokens, daemon events, dashboard system-info + component-status, runtime true-speech GET/PUT
- plugins.py: plugin listing/toggle/rescan/reload/surfaces, install/uninstall/revert/check-update, deps check/install, per-plugin conda envs (build-env, env-status, env DELETE), apps/games/themes/motions, plugin settings, HA/email/bitcoin/gcal/github/ssh routes
- knowledge.py: embedding test/integrity/reembed, memory, goals, knowledge tabs/entries/dedup, people, per-chat RAG documents, export/import
- tts.py: TTS generate/stream/preview/status/stop/test, voices, transcribe, mic active, STT VAD status/test, image upload
- agents.py: agent status/providers/dismiss, workspace run/stop/status
- vault.py: privacy vault setup/unlock/lock/rekey/move
- conversation.py: /ws/conversation WebSocket (browser conversation mode — connect starts, disconnect ends)
- store.py: read-only store proxy — plugins AND personas (status, categories, list/search, detail, persona install)
- dashboard.py: dashboard widget layout + available widgets
- backgrounds.py: scene background library (list/serve/upload/delete, webp)
- fonts.py: font registry, serve, pinned sha256-verified download
- videos.py: in-app Video Guide feed (cached)
- media.py: tool-image serving
- docs.py: doc tree, search, markdown content
- body.py: multi-body runtime (wake, health, events SSE)
Plus app-level routes in core/api_fastapi.py: /login, /logout, /setup, /api/avatar/{filename}, /plugin-web/{name}/{path}, /workspace/{project}/{path}, /cdn-cache/{path}.
KEY ENDPOINTS:
- GET /api/status — unified UI state (prompt, context, spice, streaming, TTS/STT readiness, vault state)
- GET /api/init — mega endpoint (all toolsets, prompts, personas, spices, settings in one call)
- POST /api/chat/stream — SSE streaming chat response (409 on a busy chat)
- GET /api/events — SSE event stream for real-time UI updates
CHAT FLOW:
- POST /api/chat or /api/chat/stream with {"text": "message", "chat": "optional chat name"} —
chat names the target chat; the active chat runs as before, another chat runs pinned to its own stored settings (never the active pointer)
- Response streams as SSE events (content, tool_pending, tool_start, tool_end, reload)
- POST /api/cancel (?chat= to scope) to abort; a second stream on the same busy chat returns 409. Turns are server-owned: dropping the SSE socket does NOT cancel — POST /api/chat/attach {chat?, since?} rejoins the live turn (every line carries
seq; 204 = it already finished)
- GET /api/history?chat= reads any chat by name; GET /api/chats?kind=game&slim=1 lists game/story sessions with trimmed settings; POST /api/chats {"name", "settings"} stamps settings at birth (private_chat refused there — use the vault flip)
PLUGIN MANAGEMENT:
- POST /api/plugins/install — GitHub URL or zip upload
- DELETE /api/plugins/{name}/uninstall — user plugins only
- POST /api/plugins/{name}/revert — roll back to the version retained by the last update
- POST /api/plugins/{name}/reload — hot-reload
- POST /api/plugins/rescan — discover new plugins
- PUT /api/webui/plugins/toggle/{name} — live enable/disable
MULTI-ACCOUNT CREDENTIALS (email/gcal/github):
- GET /api/{type}/accounts — list all scoped accounts
- PUT /api/{type}/accounts/{scope} — set account for scope
- DELETE /api/{type}/accounts/{scope} — remove
- Bitcoin differs: /api/bitcoin/wallets[/{scope}] (+ /check, /export) — wallets, not accounts
COMMON PATTERNS:
- Scoped endpoints use ?scope=name query param
- File uploads use multipart/form-data
- Toolsets: /api/toolsets (not /api/abilities — legacy name removed)
- Most endpoints return JSON
- 200/201 success, 400 validation, 403 auth/CSRF, 404 not found, 409 conflict (busy chat, existing vault), 503 system not ready
Perception & cadence (Game Room F3, 2026-09-09)
| Method |
Path |
Body / Query |
Notes |
| POST |
/api/perception/{chat} |
{frames: [{data, media_type}], text, source} |
Deposit what she should see next for chat (a game board, an mpv watcher). Latest wins; frames are model-only for the next unprompted turn, never persisted. 404 for an unknown or sealed chat. |
| GET |
/api/cadence/{chat} |
— |
The cadence organ's record for chat: armed, mode (event\|timer), paused, next_in, last_at, skips, pending, fired, every, pokes, ttl_in (every/pokes: event mode fires on every Nth poke; the count since the last one that became her turn). Arming is the room host plugin's business (/api/plugin/game-room/room/cadence/*). |