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:

header
Authorization: Bearer <user-jwt-token>
ℹ️
Base URL

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.

ParameterTypeDescription
messagestring (query)The user's message text.
conversation_idUUID (query, optional)Resumes an existing conversation. Omit to start a new one.
modestring (query)chat (default) or voice.

SSE event types

EventData shapeDescription
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.
example
# 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 body
{
  "request_id": "<uuid from confirmation event>",
  "approved": true
}

List conversations — GET /api/chat/conversations

Returns all conversations ordered by last activity, newest first.

response
[
  {
    "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.

response
[
  {
    "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.

response
{ "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.

response
[
  {
    "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

request body
{
  "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.

request body
{
  "query": "food preferences",
  "limit": 5
}
response
[
  { "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

response
{
  "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.

request body
{ "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.

request
Content-Type: multipart/form-data
Body: file=<wav blob>
response
{ "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.

request body
{ "text": "The weather today is sunny and 22 degrees." }

Get voice status — GET /api/voice/status

response
{
  "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.

response
{
  "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.

response
{ "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.

⚠️
Permission required

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

response
[
  {
    "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.

response
{
  "web_search": "allow",
  "write_file": "confirm",
  "launch_app": "confirm",
  "browser_open": "block"
}

Update permission — POST /api/agent/permissions/:tool_name

request body
{ "mode": "allow" | "confirm" | "block" }

List workspace files — GET /api/agent/workspace

response
[
  { "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

ParameterTypeDescription
pathstring (query)Relative path within data/workspace/.

Write workspace file — POST /api/agent/workspace/write

request body
{ "path": "notes/ideas.md", "content": "# Ideas
…" }

List tasks — GET /api/agent/tasks

response
[
  {
    "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

request body
{
  "title": "Research competitors",
  "description": "Find the top 5 competitors to Notion and summarise their pricing."
}

Browser status — GET /api/agent/browser/status

response
{ "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.

response item
{
  "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

response
{ "status": "ok", "version": "1.0.0", "uptime_seconds": 3620 }

Background processes — GET /api/system/processes

Returns all registered background processes and their current status.

response
[
  { "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

request body
{ "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

GET response
{ "ok": true, "volume": 65 }
POST request body
{ "level": 40 }

Mute — POST /api/system/volume/mute / POST /api/system/volume/unmute

Brightness — GET /api/system/brightness / POST /api/system/brightness

POST request body
{ "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

POST request body
{ "text": "text to copy" }

System info — GET /api/system/info

response
{ "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.

request body
{ "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.

response
{
  "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

request body
{
  "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

request body
{
  "title": "Design team standup",
  "start": "2024-01-16T10:00:00Z",
  "end":   "2024-01-16T10:30:00Z",
  "recurrence": "weekly"
}

Spotify

📌
OAuth required

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

response
{
  "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

request body
{ "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

request body
{ "user_id": "u1", "user_name": "Alice", "text": "What's on my agenda today?" }
response
{ "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.

response (event received)
{ "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

response
{ "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

response
{
  "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

response
[
  { "id": "uuid", "username": "alice", "role": "user", "created_at": "2024-01-15T10:00:00Z" }
]

Create user — POST /api/admin/users

request body
{ "username": "alice", "role": "user" }
response
{ "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

response
{ "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:

error response
{
  "detail": "Human-readable error description."
}
StatusMeaning
400Bad request — missing or invalid parameter.
401Unauthorized — missing or invalid Authorization: Bearer token (business variant / admin API).
429Too Many Requests — rate limit exceeded. See Retry-After header.
403Forbidden — tool is blocked by permission policy.
404Not found — resource does not exist.
500Internal server error — check the FastAPI logs.