HTTP API
The Engine HTTP API powers the Web UI and can be used to build custom integrations. It’s a REST API with Server-Sent Events (SSE) for streaming responses.
Base URL
Section titled “Base URL”http://localhost:3000/apiWhen running the Web UI image, the API is proxied through SvelteKit. When running --http-api only, it defaults to port 3100.
Authentication
Section titled “Authentication”In single-user mode (default), no authentication is required. The API is intended for local use only.
Endpoints
Section titled “Endpoints”Health
Section titled “Health”GET /healthGET /api/healthReturns {"status":"ok"}. No auth required, useful for Docker health checks.
Sessions
Section titled “Sessions”POST /api/sessions # Create a new sessionDELETE /api/sessions/:id # Delete a sessionPOST /api/sessions/:id/run # Run a task (SSE streaming response)GET /api/sessions/:id/pending-prompt # Check for a resumable promptPOST /api/sessions/:id/reply # Reply to a pending promptPOST /api/sessions/:id/abort # Abort a running taskPOST /api/sessions/:id/compact # Compact context windowRunning a task (SSE)
Section titled “Running a task (SSE)”curl -N -X POST http://localhost:3000/api/sessions/{id}/run \ -H "Content-Type: application/json" \ -d '{"task": "What is the weather in Munich?"}'The response is a Server-Sent Events stream with these event types:
| Event | Description |
|---|---|
text |
Streamed response text chunk |
thinking |
Extended thinking summary |
tool_call |
Tool invocation (name, input) |
tool_result |
Tool result (output, success) |
prompt |
Agent requests user input (ask_user). Includes promptId for resumable prompts |
secret_prompt |
Agent requests a secret (ask_secret). Includes promptId |
turn_end |
Turn completed |
changeset_ready |
File changes pending review (accept/rollback). Includes the runId the review answer must send back |
done |
Run completed |
error |
Error occurred |
Threads
Section titled “Threads”GET /api/threads # List threadsGET /api/threads/:id # Get thread detailsPATCH /api/threads/:id # Update (rename, archive)DELETE /api/threads/:id # Delete threadGET /api/threads/:id/messages # Get messages (supports pagination)GET /api/threads/:id/debug-export # Full debug bundle (thread + messages + per-run # cost/tokens/cache/composition + compaction events; # secrets scrubbed)Memory
Section titled “Memory”GET /api/memory/:ns # Read namespace (knowledge|methods|status|learnings)PUT /api/memory/:ns # Replace namespace contentPOST /api/memory/:ns/append # Append to namespacePATCH /api/memory/:ns # Update (old/new text)DELETE /api/memory/:ns # Delete entries (pattern query param)Secrets
Section titled “Secrets”GET /api/secrets # List secret namesGET /api/secrets/status # Secret status overviewPUT /api/secrets/:name # Store a secretDELETE /api/secrets/:name # Delete a secretResumable Prompts
Section titled “Resumable Prompts”Prompts (ask_user and ask_secret) are persisted in SQLite and survive SSE disconnects, page refreshes, and thread switches. The agent polls the database for answers instead of holding an in-memory callback.
- Agent calls
ask_userorask_secret→ prompt written to SQLite with apromptId - SSE event sent to client (best-effort — client may not be connected)
- Agent polls SQLite every 2s for an answer
- If client disconnects, the agent loop stays alive (polling is near-zero CPU)
- Client reconnects →
GET /api/sessions/:id/pending-prompt→ sees the prompt - Client replies →
POST /api/sessions/:id/replywithpromptId→ answer written to SQLite - Agent picks up answer on next poll → resumes execution
Prompts expire after 24 hours. On engine restart, all pending prompts are expired.
Checking for pending prompts
Section titled “Checking for pending prompts”GET /api/sessions/:id/pending-promptReturns {"pending": false} or the full prompt data:
{ "pending": true, "promptId": "uuid", "promptType": "ask_user", "question": "Shall I create the task?", "options": ["Yes", "No"], "timeoutMs": 86400000, "createdAt": "2026-04-03T23:30:00Z"}Replying to prompts
Section titled “Replying to prompts”Include promptId for idempotent replies (prevents double-answer race conditions):
POST /api/sessions/:id/replyBody: {"answer": "Yes", "promptId": "uuid"}Secret Prompt (SSE)
Section titled “Secret Prompt (SSE)”During a run, the agent may request a secret via the ask_secret tool. This triggers a secret_prompt SSE event:
event: secret_promptdata: {"promptId":"uuid","name":"STRIPE_API_KEY","prompt":"Enter your Stripe API key","key_type":"stripe"}The client stores the secret directly via PUT /api/secrets/:name (the value never enters the SSE stream), then confirms:
POST /api/sessions/:id/secret-savedBody: {"saved": true, "promptId": "uuid"}Config
Section titled “Config”GET /api/config # Get config (secrets redacted)PUT /api/config # Update configHistory & Analytics
Section titled “History & Analytics”GET /api/history/runs # List runs (filterable)GET /api/history/runs/:id # Run detailsGET /api/history/runs/:id/tool-calls # Tool calls for a runGET /api/history/stats # Aggregated statisticsGET /api/history/cost/daily # Daily cost breakdownKnowledge Graph
Section titled “Knowledge Graph”GET /api/kg/stats # Graph statisticsGET /api/kg/entities # List/search entitiesGET /api/kg/entities/:id # Entity details + relationsGET /api/tasks # List tasksPOST /api/tasks # Create taskPATCH /api/tasks/:id # Update taskDELETE /api/tasks/:id # Delete taskPOST /api/tasks/:id/complete # Mark completePOST /api/tasks/:id/stop # Stop the run in flight (202 = requested, not completed)GET /api/triggers # List schedules, watches and remindersGET /api/triggers marks each row with running (a run of it is in flight) and
stoppable (a stop can end that run; false for backups and reminders). Someone you let
in sees both only for runs they started themselves.
POST /api/tasks/:id/stop answers 202 when the stop was requested; the run can still
finish on its own. A 409 carries a code: not_running (nothing to stop),
unstoppable (the run is in a phase a stop cannot reach) or ambiguous_id (a short id
that matches more than one task). A 403 means the caller neither owns the instance nor
started the run.
Artifacts
Section titled “Artifacts”GET /api/artifacts # List artifactsPOST /api/artifacts # Save artifactGET /api/artifacts/:id # Get artifactDELETE /api/artifacts/:id # Delete artifactSubject merges
Section titled “Subject merges”GET /api/merges # Merges that can be taken back, newest firstPOST /api/merges/:id/rollback # Take one merge backA merge of two contacts or organisations writes a reversal record (~/.lynox/sweeps/merge-*.json). It holds both entries’ detail rows (email and phone for a person, domain and VAT number for an organisation), because taking the merge back has to restore them. These routes never return those rows.
A merge is listed with:
- the two names. That includes the merged-away entry’s name, which the merge removed from the contact views; a merge cannot be named for taking back without it.
- its date;
- how many data rows and conversation links it moved;
- three flags.
appliedis false for a merge that did not complete.inEffectsays the merge still holds and this record can take it back.supersededsays the same two entries were merged again later, and only the newest record can take a merge back.
inEffect is read from the contact graph, with the same check the rollback makes before it acts, so a merge already taken back says so. A rollback that would move data rows or conversation links is refused while those stores are not available, rather than reversing only the contact graph.
Records older than 90 days are removed at the next merge or engine start, the newest one excepted. A merge whose record is gone is not listed and can no longer be taken back.
An instance has one contact graph, so every signed-in user sees the same merges. A refusal is one of fixed reasons, never the store’s own message.
GET /api/crm/contacts # List contactsGET /api/crm/contacts/:name/interactions # Contact historyGET /api/crm/contacts/:name/deals # Contact dealsGET /api/crm/deals # List dealsGET /api/crm/stats # CRM statisticsIntegrations
Section titled “Integrations”GET /api/google/status # Google auth statusPOST /api/google/auth # Start device flowPOST /api/google/revoke # Revoke auth; {"delete_drive_backups": true} deletes lynox's Drive backups firstPOST /api/google/reload # Reload Google integrationGET /api/google/oauth-url # Get OAuth start URL (managed hosting)POST /api/google/claim-managed # Claim tokens from control plane (managed hosting)
POST /api/searxng/check # Validate SearXNG URLBackups
Section titled “Backups”GET /api/backups # List backupsPOST /api/backups # Create backupPOST /api/backups/:id/restore # Restore backupGET /api/files # List directoryGET /api/files/download # Download fileGET /api/files/read # Read file preview (max 1 MB)DELETE /api/files # Delete fileWorkflows
Section titled “Workflows”GET /api/workflows # List workflow runsGET /api/workflows/:id # Workflow detailsGET /api/workflows/:id/steps # Workflow step resultsGET /api/workflows/stats/steps # Step statisticsGET /api/workflows/stats/cost # Workflow cost statsDataStore
Section titled “DataStore”GET /api/datastore/collections # DataStore collectionsGET /api/datastore/:collection # Collection recordsGET /api/vault/key # Retrieve vault keyPOST /api/vault/rotate # Rotate vault keyGET /api/auth/token # Generate/retrieve auth tokenGDPR / Data
Section titled “GDPR / Data”GET /api/export # Data export (Art. 15 + Art. 20)DELETE /api/data # Data deletion (Art. 17)Migration
Section titled “Migration”Zero-knowledge self-hosted→managed transfer via X25519 ECDH + AES-256-GCM.
GET /api/migration/preview # Preview available databases and sizesPOST /api/migration/export # Start migration export (SSE streaming)GET /api/migration/handshake # Initiate ECDH key exchange (import side)POST /api/migration/handshake # Complete ECDH key exchange (import side)POST /api/migration/manifest # Receive migration manifestPOST /api/migration/chunk # Receive encrypted data chunkPOST /api/migration/restore # Restore imported dataChangeset Review
Section titled “Changeset Review”GET /api/sessions/:id/changeset # Pending file changes and the runId they belong toPOST /api/sessions/:id/changeset/review # Accept/rollback file changesThe review body is { "action": "accept" | "rollback" | "partial", "runId": "<from the GET>", "rolledBackFiles": ["<path>"] } (rolledBackFiles for partial only). The answer is refused with 409 when runId is missing or names an earlier run than the one holding the changes, and while a run is in progress.
POST /api/transcribe # Transcribe audio → text (STT, base64 in)POST /api/speak # Synthesize text → speech (TTS, SSE audio stream; needs MISTRAL_API_KEY)GET /api/thread-insights # Thread analyticsGET /api/patterns # Detected patternsGET /api/metrics # Metrics dataGET /api/api-profiles # API Store profilesGET /api/api-profiles/:id # Individual API profile