API Reference
Full HTTP endpoint documentation for every Luna router — request shapes, response shapes, and SSE event protocol.
Authentication
Personal variant routes are unauthenticated by default — run locally, no key required. The Business variant uses JWT tokens issued by the admin API.
Personal — no auth
All routes are open. Bind to 127.0.0.1 (default) to restrict to localhost only.
Business — JWT per user
Set jwt_secret in .env. The admin creates users via the admin API (authenticated with Authorization: Bearer <jwt_secret>). Each user receives a JWT token they include in their own requests:
Authorization: Bearer <user-jwt-token>All routes are relative to http://localhost:8899. In Electron builds this is accessed via the electronAPI.apiBase bridge value.
Chat
Stream a message — GET /api/chat/stream
Primary chat endpoint. Returns a Server-Sent Events stream.
| Parameter | Type | Description |
|---|---|---|
message | string (query) | The user's message text. |
conversation_id | UUID (query, optional) | Resumes an existing conversation. Omit to start a new one. |
mode | string (query) | chat (default) or voice. |
SSE event types
| Event | Data shape | Description |
|---|---|---|
metadata | {"conversation_id":"…","model":"…"} | Sent first. Carries the conversation ID and model name. |
token | {"text":"…"} | One streamed token from the LLM. |
command | {"tool":"…","input":{…}} | Luna is about to call a tool. Shown in the UI as a bracket tag. |
confirmation | {"request_id":"…","tool":"…","input":{…}} | Tool is in confirm mode — stream pauses. Send approval to POST /api/chat/confirm. |
done | {"conversation_id":"…"} | Stream complete. Safe to close the connection. |
error | {"message":"…"} | A non-recoverable error occurred. |
# Basic chat message
curl "http://localhost:8899/api/chat/stream?message=Hello+Luna" \
-H "Accept: text/event-stream"
# Resume a conversation
curl "http://localhost:8899/api/chat/stream?message=Tell+me+more&conversation_id=<uuid>" \
-H "Accept: text/event-stream"Confirm a tool call — POST /api/chat/confirm
Approves or rejects a tool call that emitted a confirmation event.
{
"request_id": "<uuid from confirmation event>",
"approved": true
}List conversations — GET /api/chat/conversations
Returns all conversations ordered by last activity, newest first.
[
{
"id": "uuid",
"title": "Conversation title (auto-generated)",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:00:00Z",
"message_count": 14
}
]Get conversation messages — GET /api/chat/conversations/:id/messages
Returns all messages in a conversation, ordered oldest first.
[
{
"id": "uuid",
"role": "user" | "assistant",
"content": "message text",
"created_at": "2024-01-15T10:30:00Z"
}
]Delete a conversation — DELETE /api/chat/conversations/:id
Permanently deletes the conversation and all its messages. Returns 204 No Content.
Get proactive message — GET /api/chat/proactive
Returns a pending unsolicited message from Luna (if any), and clears it from the queue.
{ "message": "Hey, your standup is in 10 minutes." }
// or
{ "message": null }Memory
List facts — GET /api/memory/facts
Returns all stored facts ordered by last_accessed descending.
[
{
"id": "uuid",
"content": "user prefers dark mode interfaces",
"confidence": 0.92,
"created_at": "2024-01-10T09:00:00Z",
"last_accessed": "2024-01-15T10:45:00Z"
}
]Add a fact — POST /api/memory/facts
{
"content": "user is vegetarian",
"confidence": 0.95
}Delete a fact — DELETE /api/memory/facts/:id
Returns 204 No Content.
Semantic search — POST /api/memory/search
Runs a cosine similarity search against the ChromaDB embedding index.
{
"query": "food preferences",
"limit": 5
}[
{ "id": "uuid", "content": "user is vegetarian", "score": 0.91 },
{ "id": "uuid", "content": "user doesn't like spicy food", "score": 0.87 }
]Get personality state — GET /api/memory/personality
{
"mood": 0.4,
"energy": 0.7,
"formality": 0.3,
"humor": 0.6,
"emotional_support": 0.5
}Update personality — PUT /api/memory/personality
Partial updates are supported — send only the dimensions you want to change.
{ "humor": 0.8, "formality": 0.2 }Trigger compaction — POST /api/memory/compact
Manually triggers conversation compaction. Normally runs automatically via thememory_maintenance background process.
Voice
Transcribe audio — POST /api/voice/transcribe
Accepts a WAV audio blob and returns a transcription.
Content-Type: multipart/form-data
Body: file=<wav blob>{ "text": "What's the weather like today?" }Synthesise speech — POST /api/voice/speak
Converts text to speech and plays it through the system audio output. Returns when playback is complete.
{ "text": "The weather today is sunny and 22 degrees." }Get voice status — GET /api/voice/status
{
"wake_word_enabled": true,
"listening": false,
"tts_speaking": false,
"stt_model": "base",
"tts_rate": 150
}Detect emotion — POST /api/voice/emotion
Analyses an audio blob for emotional tone. Returns a label and confidence score.
{
"emotion": "neutral",
"confidence": 0.78,
"scores": { "neutral": 0.78, "happy": 0.14, "sad": 0.05, "angry": 0.03 }
}Vision
Capture screen — POST /api/vision/screen
Takes a screenshot, sends it to the vision model, and returns a natural language description.
{ "description": "The screen shows a code editor with a Python file open…" }Capture camera — POST /api/vision/camera
Captures a frame from the default camera and returns a description.
Both vision routes require the vision tool permission to be set toallow or confirm. They default to confirm.
Agent
List skills — GET /api/agent/skills
[
{
"name": "web-search",
"version": "1.0.0",
"description": "Search the web using DuckDuckGo.",
"commands": ["web_search"]
}
]Get permissions — GET /api/agent/permissions
Returns the current permission mode for every registered tool.
{
"web_search": "allow",
"write_file": "confirm",
"launch_app": "confirm",
"browser_open": "block"
}Update permission — POST /api/agent/permissions/:tool_name
{ "mode": "allow" | "confirm" | "block" }List workspace files — GET /api/agent/workspace
[
{ "path": "notes/ideas.md", "size": 1240, "modified": "2024-01-15T10:00:00Z" },
{ "path": "research/competitors.txt", "size": 4820, "modified": "2024-01-14T09:30:00Z" }
]Read workspace file — GET /api/agent/workspace/read
| Parameter | Type | Description |
|---|---|---|
path | string (query) | Relative path within data/workspace/. |
Write workspace file — POST /api/agent/workspace/write
{ "path": "notes/ideas.md", "content": "# Ideas
…" }List tasks — GET /api/agent/tasks
[
{
"id": "uuid",
"title": "Research competitors",
"status": "in_progress",
"steps": 5,
"completed_steps": 2,
"created_at": "2024-01-15T09:00:00Z"
}
]Create task — POST /api/agent/tasks
{
"title": "Research competitors",
"description": "Find the top 5 competitors to Notion and summarise their pricing."
}Browser status — GET /api/agent/browser/status
{ "playwright_available": true, "active_page": null }Audit log — GET /api/agent/audit
Returns the last 100 audit log entries. Each entry is a JSON object.
{
"ts": "2024-01-15T10:45:22Z",
"tool": "web_search",
"input": { "query": "Luna AI competitor analysis" },
"mode": "allow",
"result": "ok",
"summary": "Returned 5 results"
}System
Health check — GET /api/system/health
{ "status": "ok", "version": "1.0.0", "uptime_seconds": 3620 }Background processes — GET /api/system/processes
Returns all registered background processes and their current status.
[
{ "name": "memory_maintenance", "status": "running", "last_run": "2024-01-15T10:40:00Z" },
{ "name": "proactive_followups", "status": "running", "last_run": "2024-01-15T10:35:00Z" },
{ "name": "calendar_reminders", "status": "running", "last_run": "2024-01-15T10:30:00Z" },
{ "name": "voice_runtime", "status": "stopped", "last_run": null }
]Launch application — POST /api/system/launch
{ "app": "notepad" }List launchable apps — GET /api/system/apps
Returns every app name Luna can launch on the current platform (profiles + registry + .desktop files).
Volume — GET /api/system/volume / POST /api/system/volume
{ "ok": true, "volume": 65 }{ "level": 40 }Mute — POST /api/system/volume/mute / POST /api/system/volume/unmute
Brightness — GET /api/system/brightness / POST /api/system/brightness
{ "level": 75 }Lock screen — POST /api/system/lock
Display off — POST /api/system/display/off
Sleep — POST /api/system/sleep
Clipboard — GET /api/system/clipboard / POST /api/system/clipboard
{ "text": "text to copy" }System info — GET /api/system/info
{ "os": "Windows", "version": "10.0.26200", "machine": "AMD64", "ram_gb": 32.0, "battery_pct": 87 }Generate 3D scene — POST /api/system/scene
Asks the LLM to generate a Three.js scene description for the dynamic overlay.
{ "prompt": "A calm ocean at night with stars" }Get app state — GET /api/state
Returns the persisted UI state (sidebar view, active conversation, etc.).
Set app state — PUT /api/state
Persists UI state across sessions.
Luna dashboard
Dashboard summary — GET /api/luna/dashboard
Returns aggregated weather, market, and news data in a single call.
{
"weather": {
"temperature": 22.4,
"condition": "partly cloudy",
"humidity": 58,
"wind_kph": 14
},
"markets": [
{ "symbol": "SPY", "price": 521.40, "change_pct": 0.32 }
],
"news": [
{ "title": "…", "source": "BBC", "url": "…", "published_at": "2024-01-15T08:00:00Z" }
]
}Weather — GET /api/luna/weather
Returns current conditions from Open-Meteo. No API key required.
Markets — GET /api/luna/markets
Returns configured stock and crypto quotes. Requires alpha_vantage_key in.env for equities.
News — GET /api/luna/news
Returns recent headlines. Requires news_api_key for TheNewsAPI.
Calendar
List tasks — GET /api/calendar/tasks
Create task — POST /api/calendar/tasks
{
"title": "Finish PR review",
"due_date": "2024-01-16T17:00:00Z",
"priority": "high"
}Update task — PUT /api/calendar/tasks/:id
Delete task — DELETE /api/calendar/tasks/:id
List events — GET /api/calendar/events
Create event — POST /api/calendar/events
{
"title": "Design team standup",
"start": "2024-01-16T10:00:00Z",
"end": "2024-01-16T10:30:00Z",
"recurrence": "weekly"
}Spotify
Spotify routes require completing the OAuth flow atGET /api/spotify/login first. See the Environment page for setup instructions.
Login — GET /api/spotify/login
Redirects to Spotify's OAuth authorisation page.
Playback state — GET /api/spotify/status
{
"is_playing": true,
"track": "Midnight City",
"artist": "M83",
"album": "Hurry Up, We're Dreaming",
"progress_ms": 84000,
"duration_ms": 243733
}Play / pause — POST /api/spotify/play / POST /api/spotify/pause
Skip track — POST /api/spotify/next
Previous track — POST /api/spotify/previous
Set volume — POST /api/spotify/volume
{ "volume_percent": 60 }Channels
Messaging channel webhooks for the Business variant. Each channel user gets an isolated conversation thread. UI-specific commands are stripped from replies automatically.
Telegram webhook — POST /api/channels/telegram
Receives Telegram Bot API updates. Luna replies asynchronously via the Bot API. Register with: curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://HOST/api/channels/telegram"
Discord interactions — POST /api/channels/discord
Receives Discord interaction payloads. Ed25519 signature verified on every request. Handles type-1 PING automatically. Set as the Interactions Endpoint URL in the Discord Developer Portal.
Slack events — POST /api/channels/slack
Receives Slack Events API payloads. HMAC-SHA256 signature verified. Handles theurl_verification challenge. Subscribe to message.channels andapp_mention events.
Generic webhook — POST /api/channels/webhook
{ "user_id": "u1", "user_name": "Alice", "text": "What's on my agenda today?" }{ "reply": "You have 2 tasks due today…", "user_id": "u1" }GitHub webhook — POST /api/channels/github
Receives GitHub webhook events. Verifies the X-Hub-Signature-256 header using HMAC-SHA256 when github_webhook_secret is set. Handlespush, pull_request, issues, issue_comment, and release events. Returns a ping response on first registration.
{ "ok": true, "event": "push", "summary": "[GitHub] alice pushed 2 commits to owner/repo/main: fix login bug" }If github_notify_slack_channel or github_notify_telegram_chat_id is set in .env, Luna forwards the summary to that channel automatically.
Channel status — GET /api/channels/status
{ "telegram": true, "discord": false, "slack": true, "github": true, "webhook": true }Admin
Business variant user and token management. All endpoints requireAuthorization: Bearer <jwt_secret> (the jwt_secret value from .env).
System info — GET /api/admin/info
{
"provider": "anthropic",
"model": "claude-sonnet-4-5",
"jwt_enabled": true,
"rate_limit_enabled": true,
"rate_limit_per_minute": 60,
"user_count": 4,
"channels": { "telegram": true, "discord": false, "slack": true }
}List users — GET /api/admin/users
[
{ "id": "uuid", "username": "alice", "role": "user", "created_at": "2024-01-15T10:00:00Z" }
]Create user — POST /api/admin/users
{ "username": "alice", "role": "user" }{ "user_id": "uuid", "username": "alice", "token": "eyJhbGci..." }Delete user — DELETE /api/admin/users/:id
Returns 204 No Content. The user's JWT is immediately invalidated.
Rotate token — POST /api/admin/users/:id/rotate-token
{ "token": "eyJhbGci..." }LLM providers — GET /api/admin/llm/providers
Returns all 8 configured providers and which is currently active.
Error format
All error responses use a consistent JSON envelope:
{
"detail": "Human-readable error description."
}| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameter. |
401 | Unauthorized — missing or invalid Authorization: Bearer token (business variant / admin API). |
429 | Too Many Requests — rate limit exceeded. See Retry-After header. |
403 | Forbidden — tool is blocked by permission policy. |
404 | Not found — resource does not exist. |
500 | Internal server error — check the FastAPI logs. |