API Reference · Developers · Public Edge surface · Applies to 5.0.x
This page consolidates the previous API pages into one maintainable reference. It documents only the customer-facing public API reachable through the Edge with API-key or session authentication.
This page is a curated subset, not an index. It covers the endpoints most callers need; the product serves far more, and an endpoint's absence here is not evidence it does not exist.
Public API calls use an API key or an authenticated session. Tenant scope is enforced by the Edge. Cross-tenant access returns an authorization error even when the path shape is otherwise valid.
Header
Use
X-API-Key
Tenant-scoped API access for integrations.
Content-Type: application/json
Required for JSON request bodies.
Authorization: Bearer
Session/JWT access where enabled by the deployment.
OpenAI-compatible
POST/v1/chat/completions
An unknown model does not fail. If no backend serves the model you asked for, the request is answered by a different one rather than rejected. Measured 2026-08-20 on two nodes with a made-up id:
Two things follow for a client. The response's model field names the model that answered, not the one you requested — so comparing it against your request is how you detect a swap in the body. And the substitute is not fixed: it depends on which node serves the request and on which models that node currently has loaded — the same made-up id returned qwen3.5:397b from one node on 19 August and mistral-large-3:675b from the same node a day later. Do not cache the mapping.
If you need the model you asked for, check the header or the model field before trusting the answer. A 200 is not a confirmation that your model ran. Substitution works the same way with stream: true — the header is sent and every frame's model field names the substitute (verified on two nodes, 2026-08-20).
Streaming errors do not arrive as an error status. When a request fails after the stream has been opened, the HTTP status can be 200 and the failure arrives inside the first frame — sometimes as a JSON string nested in detail, so it needs decoding twice. Measured on two nodes, 2026-08-20, omitting messages:
stream: false -> 400 {"error":{"message":"[] is too short - 'messages' …"}}
stream: true -> 200 data: {"detail":"{"error":{"message":"[] is too short …"}}"}
Not every error diverges — a missing model returns 400 on both paths. So with stream: true, read the first frame before treating a 200 as success.
GET/v1/models
Return the model catalogue available to the caller. There is no per-model detail endpoint on this API and no legacy /v1/completions; use /v1/chat/completions.
POST/v1/chat/completions
Create a chat completion. Supports streaming and compatible tool fields where enabled.
POST/v1/embeddings
Create embeddings.
POST/api/v1/models/show
Return model detail by request body.
Chat and conversations
Not yet implemented. Re-measured 2026-08-19 path by path: /api/v1/conversations and everything under it appears nowhere in the 5.0 source. A designed surface, not a shipped one — do not build a client against it yet. There is, however, a conversation surface today, at different paths:GET|POST|PATCH|DELETE /api/v1/edge/conversations, GET|POST /api/v1/edge/conversations/messages, plus /export and /search; and GET /api/v1/users/me/conversations. It is keyed by session rather than by a path id, so it is a different shape, not the same API at another prefix. branch exists nowhere; conversation share does not either (/api/v1/artifacts/share/ shares artifacts, not conversations), and artifacts are a top-level surface at /api/v1/artifacts, not nested under a conversation. Measured 2026-08-20.
GET/chat
Open the browser chat shell.
GET/login
Open the login form when session authentication is used.
GET/health
Public health check — reachable without a token, and the quickest confirmation that the daemon is up. It reports the running version. Note the path has no /api/v1 prefix.
Served today — the session-keyed conversation API
Verified against the 5.0 source on 2026-08-20. These are keyed by the caller's session, not by a path id, so they are a different shape from the designed resource API below — not the same endpoints at another prefix.
GET/api/v1/edge/conversations
List the caller's own conversations.
POST/api/v1/edge/conversations
Create one. JSON body optional; title is honoured when present.
PATCH/api/v1/edge/conversations
Rename. JSON body; a malformed body is rejected with 400.
DELETE/api/v1/edge/conversations
Delete the caller's own conversations. Self-scoped by session — this is the "clear my history" action and the Article 17 erasure path.
GET/api/v1/edge/conversations/messages
Messages of one conversation. Requires ?id=; without it the call returns 400.
POST/api/v1/edge/conversations/messages
Append a message. The JSON body carries conversation_id.
GET/api/v1/edge/conversations/search
Flat list of {conversation_id, title, role, snippet, ts}, newest first. No pagination — capped at 50 hits.
GET/api/v1/edge/conversations/export
Export a conversation as markdown or plain JSON.
GET/api/v1/users/me/conversations
The caller's conversations as the controller sees them.
Mostly not yet implemented — with one exception. Re-measured 2026-08-19 path by path: /api/v1/auth/loginis served. The others here — the /api/v1/me/* group, /api/v1/identity/{me,register,{id}}, /api/v1/auth/{logout,refresh,2fa/*} — appear nowhere in the 5.0 source at those paths. One of them is a wrong path rather than a missing feature: caller settings are served today at GET|PUT /api/v1/users/me/settings (both methods registered, measured 2026-08-20). preferences exists nowhere under any prefix; quotas, tools and models exist only as different surfaces with different scope (/api/v1/metering/quotas, /api/v1/agent/tools, /api/v1/models) — not as per-caller views, so treat them as unimplemented rather than renamed. And user records are served at /api/v1/identity/users/{id}, not the /api/v1/identity/{id} shown below: a wrong path rather than a missing one.
Stub — returns no results, by construction. Measured 2026-08-20: the handler answers {"results":[],"count":0,"_stub":true}, and nothing in 5.0 persists a message for it to search — comm messages live in memory only (a capped inbox and an ephemeral outbox), and 4.x’s message store is on disk but not compiled into the daemon. Branch on _stub: an empty results array here does not mean “nothing matched”.
POST/api/v1/comm/calls/start
Start an outbound voice call.
GET/api/v1/science/sources
List enabled science sources.
GET/api/v1/science/sources/categories
List science source categories.
POST/api/v1/science/sources/request
Request source enablement.
GET/api/v1/science/tools
List role-filtered science tool schemas.
POST/api/v1/science/tools/execute
Execute a science tool.
GET/api/v1/{category}/sources
List enabled sources through a category alias.
GET/api/v1/iot/devices
List IoT devices.
POST/api/v1/iot/devices/{id}/read
Read a device attribute.
POST/api/v1/iot/devices/{id}/write
Write a device attribute.
GET/api/v1/swarms
List swarms.
POST/api/v1/swarms
Create a swarm.
POST/api/v1/swarms/{id}/goal
Set a swarm goal.
GET/api/v1/tenants/{tenantId}/theme
Read tenant theme.
GET/api/v1/tenants/{tenantId}/branding/logo
Read tenant logo.
Endpoint detail pattern
Each endpoint should stay maintainable by following the same reference shape. This answers the annotated concern directly: the template is not placeholder UI; it is the required structure for real endpoint entries.
POST/example/path
Purpose
When to use this endpoint and which user or system action it supports.
Auth and permission
Required API key/session, tenant scope and role/capability expectation.
Parameters
Path, query and body fields with type, required state and short notes.
Request example
Copyable curl and JSON example using placeholder host and key variables.
Response
Status codes, response schema summary and a compact example.
Errors
Common failure cases and sanitized public meanings.
Notes
Tenant, version and compatibility notes without internal implementation details.
Errors
Status
Meaning
400
Request shape or validation failed.
401
Missing or invalid authentication, or intentionally private path.
403
Authenticated but not allowed in this tenant or role.
404
Resource not found or not visible in this tenant.
429
Rate limit or quota exceeded.
503
Public Edge cannot serve the requested capability.
Every error body carries an error field holding a short machine-readable code, and a detail field holding a human-readable sentence. Match on error; treat detail as text for logs and operators, not as a value to branch on.
{"error":"not_found","detail":"no handler is registered for this path","path":"/v1/completions"}
On /api/v1/*, a rejected call tells you nothing about whether the path exists. Authentication is checked before the route is looked up, so a call without a key returns 401 — and a call with a key the node does not accept returns 403 — whether or not the endpoint is served. Both codes look identical for a real endpoint and for a path that was never registered. Only a call the node accepts distinguishes them, so never infer that an endpoint is missing from a 401 or a 403.
What the two codes do tell you is whether a credential was presented. 401 means none arrived; 403 means one arrived and this node refused it, with a reason field in the body naming the refusal. A 401 also carries WWW-Authenticate. So a 403 is a signal to look at your key, and a 401 is a signal that it never got sent — a proxy stripping the header, or the wrong header name.
reason depends on the shape of what you sent, and only then on the node. A credential that is not a capability token at all reads as bad_encoding everywhere, consistently. A correctly-shaped token can read as bad_encoding on one node and unknown_key on another.
unknown_key means the token is well formed but the key named in it could not be found. The most common cause is that the tenant it was issued for no longer exists — a terminated tenant's key is dropped, and tokens signed with it then fail this way rather than as a bad signature, so that a terminated tenant can be told apart from a tampered token. Check the tenant before you check the node.
And do not conclude a token is malformed from a single node. If a capability-shaped token is refused as bad_encoding, try another node before assuming the token is broken.