REST API
HTTP API surface — all endpoint groups, authentication, pagination, errors, and rate limits.
Sophon exposes every feature through a REST API at /api/* on the Gateway. The Dashboard, CLI, and Mobile app all use the same API; there are no internal endpoints.
Base URL
https://<gateway>/apiDefault Gateway port in development: 8080.
Authentication
| Mode | Used by | Header |
|---|---|---|
| JWT Bearer | Dashboard, CLI after sophon login, Mobile | Authorization: Bearer <jwt> |
| API tokens | CI, scripts, long-lived integrations | Authorization: Bearer sk_... |
| OIDC SSO | Enterprise SSO-configured deployments | JWT from IdP after OIDC exchange |
| No auth | Personal tier with AutoAdmin: true in config (dev only) | — |
Tokens are user-scoped. Multi-tenant deployments also require tenant context (see Tenants).
Endpoint groups
All endpoints live under /api. Major groups:
Identity & auth
POST /auth/login— email + password → JWTPOST /auth/logout— revoke current sessionPOST /auth/refresh— renew JWT before idle timeoutPOST /auth/tokens— create API tokenGET /auth/me— current user infoGET /auth/sessions— active sessions for current userDELETE /auth/sessions/{id}— revoke a session
Agents
GET /agents/POST /agents/GET|PATCH|DELETE /agents/{id}GET|PUT /agents/{id}/soul— read/write SOUL.mdGET|PUT /agents/{id}/boot— BOOT.mdGET|PUT /agents/{id}/heartbeat— HEARTBEAT.mdGET|PUT /agents/{id}/tools— tool allowlist
Chat + sessions
POST /chat— send a message synchronously (returns immediately, task runs in background)GET /sessions— listGET /sessions/{id}— describeGET /sessions/{id}/messages— message historyPOST /sessions/{id}/fork— fork a sessionDELETE /sessions/{id}— delete
Memory
GET /memory/entries— paginated list with filters (scope, agent, query); addincludeSuperseded=trueto also return superseded (historical) factsPOST /memory/entries— create; optionalsupersedes: "mem_..."marks an existing entry as superseded by the new onePUT|DELETE /memory/entries/{id}GET /memory/search?q=&scope=&limit=GET /memory/graph/all?nodeLimit=&entryDays=&kinds=&scope=— full-graph overview (powers the graph explorer)GET /memory/graph?seed=&depth=&limit=— subgraph cluster seeded at an entry or entity idGET /memory/graph/mermaid— Mermaid export of the overview graph (same filters, plusdirection=LR|TDandincludeEntries)GET /memory/entities/duplicates?threshold=&limit=— candidate duplicate entity pairsPOST /memory/entities/{id}/merge— merge a duplicate entity into a survivorPOST /memory/entities/duplicates/{pairKey}/dismiss— dismiss a candidate pairPOST /memory/vectors/reindex— purge and rebuild the current user's memory vectorsGET /memory/logs?agentId=&days=— daily logs
Workflows
GET|POST /workflowsGET|PATCH|DELETE /workflows/{id}POST /workflows/{id}/triggerPOST /workflows/{id}/pause/POST /workflows/{id}/resumePOST /workflows/{id}/cloneGET /workflows/{id}/runsGET /workflows/{id}/runs/{runId}GET /workflows/{id}/history
Skills
GET /skills— installedPOST /marketplace/install— install a marketplace package by nameDELETE /skills/{name}GET /skills/{name}— manifest + detailsPOST /skills/author— generate via LLMPOST /skills/{name}/enable/DELETE /skills/{name}/enable(per-agent)
Plugins (requires Admin role)
GET /plugins— loaded plugins plus current enabled/allowlist stateGET /plugins/{id}— plugin detailPOST /plugins/{id}/restart/POST /plugins/{id}/stop— hot start/stop, no Gateway restartPOST /plugins/scan— rescan plugin directories and load newly installed pluginsPUT /plugins/settings— toggleSophon:Plugins:Enabledand the per-plugin allowlist (hot-reloaded)
Documents
POST /documents/upload— multipart/form-dataPOST /documents/ingest-url— save a URL as a document (SSRF-guarded; HTML becomes a readable doc, files are saved as-is)GET /documents— paginated, filterableGET /documents/{id}— metadata + extracted textGET /documents/{id}/download— original filePOST /documents/{id}/summarizePOST /documents/{id}/ask— grounded Q&A over one document; answers carry[n]citations resolved intocitations[]POST /documents/ask— grounded Q&A across the whole library (grounded: falsewhen nothing relevant is found)PUT /documents/{id}/content— replace content in place: same id, previous revision archived, version bumped, text re-extractedGET /documents/{id}/versions— version historyGET /documents/{id}/versions/{version}/download— download a specific archived versionDELETE /documents/{id}
Channels
GET /channels— list configuredPOST /channels— addGET|PATCH|DELETE /channels/{id}POST /channels/{id}/test
Connections
GET /connectionsPOST /connections/{service}/authorize— start OAuth flowGET /connections/{service}/callback— OAuth callbackPOST /connections— API key / manual credentialsGET|DELETE /connections/{id}POST /connections/{id}/testPOST /connections/{id}/rotate
Cron jobs
GET|POST /cronGET|PATCH|DELETE /cron/{id}POST /cron/{id}/triggerPOST /cron/{id}/pause/POST /cron/{id}/resumeGET /cron/{id}/history
Webhooks
GET|POST /webhooksGET|PATCH|DELETE /webhooks/{id}POST /webhooks/{id}/testGET /webhooks/{id}/deliveriesPOST /webhooks/{id}/deliveries/{deliveryId}/retryPOST /webhooks/{id}/rotate-secretPOST /webhooks/receive/{slug}— inbound webhook target
Tasks
GET /tasks/activeGET /tasks/historyGET /tasks/{id}POST /tasks/{id}/cancel
Canvas
GET /canvas?session=— list canvases in a sessionGET /canvas/{id}POST /canvas/{id}/forkGET /canvas/{id}/export— zip of filesDELETE /canvas/{id}
Claude Code
GET|POST /claude-codeGET|DELETE /claude-code/{id}POST /claude-code/{id}/messages— send a messageGET /claude-code/{id}/events— event stream (SSE)POST /claude-code/{id}/exec— shell commandGET /claude-code/{id}/export— zip of project
Discussions
GET|POST /discussionsGET|PATCH|DELETE /discussions/{id}POST /discussions/{id}/runs— start a runGET /discussions/{id}/runs— list runsGET /discussions/runs/{runId}POST /discussions/runs/{runId}/cancel
Insights
GET /insights/metrics?window=GET /insights/summaryGET /insights/cardsPOST /insights/cards/{id}/dismissPOST /insights/query
Approvals
GET /approvals— pendingGET /approvals/{id}POST /approvals/{id}/approvePOST /approvals/{id}/edit— approve with modificationsPOST /approvals/{id}/rejectGET /approvals/history
Info requests
Structured questions an agent asks mid-task (the ask tool). Pending questions are in-memory; history persists as session events.
GET /info-requests/pending?sessionId=— pending questions (used to recover question cards after reconnect)POST /info-requests/{id}/respond— answer a pending question when the SignalR connection is down
Voice
Capabilities
GET /voice/capabilities— the one call that answers "what can this server do for voice right now"
It returns availability flags plus the full provider lists:
| Field | Meaning |
|---|---|
nativeInputSupported / nativeOutputSupported | The client may use its own speech recognition and speech synthesis — always true; voice works with no provider configured |
serverTranscriptionAvailable | At least one speech-to-text provider is active and healthy |
providerSpeechAvailable | At least one text-to-speech provider is active and healthy |
handsFreeSupported | Server-side hands-free listening is available — computed from the active speech-to-text count, because any active provider can be endpointed. It says nothing about the Dashboard's provider-free browser path, which works without one |
sttProviders / ttsProviders | Every configured provider, each with status, vendor, streaming support and (for speech-to-text) supportsEndpointing and languages |
singleVendorOptions | Vendors that are active on both sides, for clients that want one vendor end to end |
status is active, inactive or error. That is what lets a client tell three states apart that used to look alike: a provider that is not configured is simply absent from the list; one that is configured but failing its health checks is present with status: "error" and excluded from the availability booleans; a healthy one is active. A provider is only marked failing after repeated failed health checks, so the status lags a real outage — see Voice.
Preferences (per user)
GET|PUT /voice/preferences— language, speed, input/output mode, default provider, conversation mode, silence and inactivity limits, and the personal speech-to-text overridesGET|PUT /voice/settings— compatibility alias for the same handlers
PUT replaces the stored preferences, so read-modify-write: send back every field you were given, with your changes applied.
Transcription
POST /voice/transcribe—multipart/form-data, one audio file (the first file in the form). Up to 10 MB;audio/*, or a.wav,.mp3,.m4a,.ogg,.webmor.flacextension. Returns{ text }. Answers 503transcription_unavailablewhen no provider can serve it, 502transcription_failedwhen the provider errorsPOST /voice/transcriptions— alias for the same handler
Text-to-speech providers (writes require Admin)
GET /voice/providers— id, name, type, voice, priority,status, last health checkPOST /voice/providers— add a provider (Admin)DELETE /voice/providers/{id}— remove a provider (Admin)GET /voice/providers/{id}/voices?language=— the provider's voice catalogPATCH /voice/providers/{id}/voice— set which voice this provider speaks with (Admin)POST /voice/providers/{id}/test— health check; returns{ healthy, error }(Admin)POST /voice/providers/{id}/synthesize-test— synthesize a sample line and return it as base64 audio (Admin)
Speech-to-text providers and host defaults
GET /stt/providers— configured speech-to-text providersPOST /stt/providers— add a provider (Admin)DELETE /stt/providers/{id}— remove a provider (Admin)POST /stt/providers/{id}/test— health check (Admin)GET /stt/providers/{id}/languages— the provider's language listGET /stt/settings— host-wide listening defaults (default provider, interim results, endpointing, maximum utterance)PUT /stt/settings— update those defaults (Admin). Per-user overrides live on/voice/preferencesinstead
Node voice runtime (Admin; 403 otherwise)
GET /voice/runtime/nodes— voice runtimes across paired nodesGET|PUT /voice/runtime/nodes/{id}— read or configure one node's voice runtimePOST /voice/runtime/nodes/{id}/start— start a talk session. Body:{ agentId, sessionId }, both optional but the object itself required. A session the node's owner does not own is ignored and a fresh one is startedPOST /voice/runtime/nodes/{id}/stop/POST /voice/runtime/nodes/{id}/interrupt
Node-token routes (off by default)
GET /nodes/me/voice/runtime,POST /nodes/me/voice/state,POST /nodes/me/voice/wake
These three authenticate with a node token, and all three answer 404 until an operator sets Sophon:Voice:NodeWake:Enabled=true. The check runs before authentication, so a disabled Gateway never reveals whether a token is valid. With the feature on, /wake additionally requires the node's voice permission, is rate-limited per node and rejects an over-long transcript. No shipped client calls these routes; nodes do not listen on their own. See Voice.
Home feed
GET /home— composed home feed (hero, insights, thought, summary, on-this-day, active tasks)
Sophon Node
GET /nodes— paired nodesGET /nodes/pending— nodes awaiting approvalGET /nodes/{id}— describe one nodePOST /nodes/{id}/approve/POST /nodes/{id}/revokePUT /nodes/{id}/permissions— set permission scopesPOST /nodes/{id}/rotate-token— issue a fresh node tokenGET|POST /nodes/{id}/commands— list or enqueue a commandGET /nodes/commands/{commandId}/wait— long-poll for a command resultPOST /nodes/commands/{commandId}/cancelGET /nodes/{id}/audit— the node's command audit trailGET /nodes/{id}/crashes— crash reports from the nodePOST /nodes/pairing/request— start pairingGET /nodes/pairing/{nodeId}/status— poll pairing state (node-side, unauthenticated)
Node-token routes, called by the node itself rather than by a user: GET /nodes/me, POST /nodes/me/heartbeat, GET /nodes/me/commands, POST /nodes/me/commands/{commandId}/progress, POST /nodes/me/commands/{commandId}/complete, POST /nodes/me/crashes, and GET /nodes/update-manifest. The command queue is reachable over plain HTTP, but the shipped node takes its commands over the hub.
MCP
GET|POST /mcp/servers— client-side MCP server registrationsGET|PATCH|DELETE /mcp/servers/{id}POST /mcp/servers/{id}/testGET /mcp/servers/{id}/toolsGET /mcp/server/status— own server statusGET|PUT /mcp/server/configGET|POST /mcp/server/tokensDELETE /mcp/server/tokens/{id}GET /mcp/sse/GET /mcp/http— transport endpoints (MCP clients connect here; not for general REST callers)
Push notifications
POST /push/devices— registerGET /push/devicesDELETE /push/devices/{id}GET|PUT /push/devices/{id}/preferencesPOST /push/test
Admin (requires Admin role)
GET|POST /admin/usersGET|PATCH|DELETE /admin/users/{id}POST /admin/users/{id}/logoutGET|POST /admin/rolesGET|PATCH|DELETE /admin/roles/{id}GET /admin/audit?filter=...GET /admin/audit/exportGET|POST /admin/licensePOST /admin/license/verifyGET|POST /admin/onboarding— remote-access tickets
Tenants (Enterprise)
GET|POST /admin/tenantsGET|PATCH|DELETE /admin/tenants/{id}GET /admin/tenants/{id}/users— tenant membershipPOST /admin/tenants/{id}/users— assign userPOST /admin/tenants/{id}/exportPOST /admin/tenants/import
Operations (Operator/Admin)
GET /ops/health— deep health checkGET /ops/system— resource usagePOST /ops/pause/POST /ops/resume/POST /ops/drainPOST /ops/services/{name}/restartPOST /ops/caches/{name}/flushPOST /ops/diagnostics/runGET /ops/diagnostics/bundle
Compliance
POST /compliance/reports— generateGET /compliance/reports/{id}GET /compliance/reports/{id}/downloadPOST /compliance/dsar— data subject access requestGET|POST /compliance/retentionPOST /compliance/access-reviews
Backup (requires ManageSettings — backups contain every user's data)
POST /backup— create a full data-directory backup; asynchronous: returns 202 Accepted (409 if one is already running)GET /backup/status— progress of the current / result of the last backup runGET /backup— list backupsDELETE /backup/{name}— delete a backup (strict name validation)
Public
GET /health— shallow health (load balancers)GET /versionGET /tools— available tool catalog
Pagination
List endpoints support offset or cursor pagination (depending on resource):
GET /memory/entries?limit=50&cursor=<opaque>Response:
{
"items": [...],
"nextCursor": "eyJpZCI6Im1lbV94eXoiLCJ...",
"hasMore": true
}Cursor tokens are opaque — don't parse them, just pass them back.
Error format
{
"error": {
"code": "not_authorized",
"message": "Missing required permission: ManageWorkflows",
"details": { "permission": "ManageWorkflows" },
"requestId": "req_abc123"
}
}Common codes:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body / params |
| 401 | unauthenticated | Missing / expired token |
| 403 | not_authorized | Missing permission / tenant mismatch |
| 404 | not_found | Resource doesn't exist or you can't see it |
| 409 | conflict | Resource state conflict (e.g., pair already complete) |
| 413 | payload_too_large | Over upload / body size limit |
| 422 | validation_failed | Schema validation error |
| 429 | rate_limited | Hit a rate limit (headers include Retry-After) |
| 500 | internal_error | Unexpected |
| 502 | upstream_unavailable | LLM provider / external service failed |
| 503 | service_unavailable | Gateway in drain mode |
Include X-Sophon-Request-Id with support tickets.
Rate limits
Default per-user rate limits (configurable):
| Category | Limit |
|---|---|
| Read-heavy (GET list / describe) | 120/min |
| Write-heavy (POST, PATCH, DELETE) | 60/min |
| Approval / cancel | 30/min |
| Expensive (doc upload, backup, DSAR) | 10/min |
| Send message / chat | 60/min |
Exceeding limits returns 429 with Retry-After header. Limits are per-user; API tokens have separate per-token limits.
Content types
- Requests:
application/jsonunless otherwise noted - Responses:
application/json - Uploads:
multipart/form-datafor files - Event streams (Claude Code):
text/event-stream(SSE)
Tenant context
For multi-tenant deployments, specify tenant via:
X-Sophon-Tenant: acmeOr use tenant-scoped URL prefixes:
/api/tenants/acme/workflowsSee Tenants.
OpenAPI
The Gateway exposes Swagger / OpenAPI at:
https://<gateway>/swagger
https://<gateway>/swagger/v1/swagger.jsonUse this to generate SDKs in any language:
npx @openapitools/openapi-generator-cli generate \
-i https://gw.example.com/swagger/v1/swagger.json \
-g typescript-fetch \
-o ./sdk