API Reference
Complete reference for the Cephable Automate HTTP Server — base URL, authentication, every endpoint, request and response schemas, status codes, and error shapes.
Base URL
http://127.0.0.1:4317
The server binds 127.0.0.1 only — never 0.0.0.0, never a LAN address. Remote hosts cannot reach it.
If the preferred port is occupied, Cephable tries the next eleven ports (4317–4328) and reports the one it bound. Always read the endpoint from the extension detail view, an environment variable, or a discovery sweep — never assume 4317. See Finding the port.
http://localhost:4317 usually works too, but prefer the literal 127.0.0.1: on some systems localhost resolves to ::1 first, and the server is not listening there.
Authentication
Every route, including /health, requires the access key as a bearer token:
Authorization: Bearer <access key>
- Comparison is constant-time. A missing, malformed, or wrong token returns
401before routing, so an unauthenticated request cannot even discover which paths exist. - The key is generated by the app (32 random bytes, base64url — 43 characters), stored encrypted in OS secure storage, and must be at least 24 characters.
- Regenerating the key in the app invalidates the old one immediately.
- The key is deliberately not accepted as a command-line argument, because operating systems expose process command lines to other local processes.
Conventions
| Content type | application/json; charset=utf-8 on every response, with Cache-Control: no-store |
| Request bodies | JSON. Max 1 MiB — larger bodies are rejected with 400 |
| Concurrency | One run at a time. /v1/runs, /v1/chat/completions, and /v1/automate/models/select return 409 while a run is in flight |
| Streaming | Not supported. Run endpoints block until the run reaches a terminal state |
| CORS | No CORS headers and no OPTIONS handler. A browser page on an http(s):// origin cannot call this server; use a native, Node, Electron main-process, or server-side client |
| Method mismatch | Returns 404, not 405 — GET /v1/runs is "not found" |
Endpoint summary
| Method | Path | Purpose | Allowed while busy |
|---|---|---|---|
GET |
/health |
Readiness, runtime, device, and workspace info | Yes |
GET |
/v1/models |
OpenAI-shaped model list (always cephable-agent) |
Yes |
GET |
/v1/automate/models |
The on-device GGUF catalog with availability | Yes |
POST |
/v1/automate/models/select |
Pin a model for this app session | No — 409 |
POST |
/v1/automate/cancel |
Stop whatever is running, whoever started it | Yes |
POST |
/v1/runs |
Run an Automate task (native contract) | No — 409 |
POST |
/v1/runs/{resumeToken}/tool-results |
Resume a run parked on your own tools | Yes — by design |
POST |
/v1/chat/completions |
Run an Automate task (OpenAI contract) | No — 409, unless it carries tool results for a parked run |
GET /health
No parameters. Returns the state of the server, the agent worker, the model, and the device.
curl -sS http://127.0.0.1:4317/health -H "Authorization: Bearer $CEPHABLE_AUTOMATE_KEY"
| Field | Type | Notes |
|---|---|---|
status |
"ok" |
Constant |
service |
"cephable-agent" |
Use this to confirm a listener is Cephable when sweeping ports |
appVersion |
string |
Desktop app version |
platform |
string |
win32 · darwin · linux |
architecture |
string |
x64 · arm64 |
osRelease |
string |
OS kernel/release string |
cpu |
string |
First CPU model string, or "unknown" |
logicalCpuCount |
number |
Logical cores |
totalMemoryBytes |
number |
Physical RAM |
workflowStatus |
string |
Agent status — see workflow statuses |
activeRequestId |
string | null |
The run in flight, if any |
modelName |
string | null |
GGUF file name currently loaded |
workerRunning |
boolean |
Whether the workflow utility process is warm |
busy |
boolean |
Whether this server is executing a run — including one parked awaiting your tool results |
awaitingToolResults |
boolean |
A run is parked awaiting caller-executed tool results. Lets a client recovering from a crash tell "someone else's run" from "my own run waiting on me" |
workspace |
string |
Absolute path of the Automate workspace folder |
backend |
object | null |
{ flavorId, accelerator, cpuFallback } — the llama.cpp acceleration actually in use |
contextSize |
number | null |
The context window this device got. Sized from system RAM, so it varies per machine — record it in any comparison between runs |
launchModelSelectionError |
string | null |
Why a --model/--family/--size launch argument could not be applied |
automateModelSelection |
object | null |
The model pinned via /v1/automate/models/select, or null when the app's own selection is in effect |
Ready to run means busy === false and workflowStatus is idle or terminated.
{
"status": "ok",
"service": "cephable-agent",
"appVersion": "4.2.1",
"platform": "win32",
"architecture": "x64",
"osRelease": "10.0.26200",
"cpu": "AMD Ryzen 9 7940HS",
"logicalCpuCount": 16,
"totalMemoryBytes": 68719476736,
"workflowStatus": "idle",
"activeRequestId": null,
"modelName": "gemma-4-4b-it-Q4_K_M.gguf",
"workerRunning": true,
"busy": false,
"workspace": "C:\\Users\\you\\AppData\\Roaming\\Cephable\\automate-http-workspace",
"backend": { "flavorId": "vulkan", "accelerator": "vulkan", "cpuFallback": false },
"contextSize": 16384,
"launchModelSelectionError": null,
"automateModelSelection": null
}
GET /v1/models
The OpenAI-compatible model list. There is exactly one entry, and it represents the whole assistant rather than a raw model.
{
"object": "list",
"data": [{ "id": "cephable-agent", "object": "model", "owned_by": "cephable" }]
}
To see the actual GGUF catalog, use /v1/automate/models.
GET /v1/automate/models
The on-device model catalog as Cephable knows it, including which entries this device can run and which are already downloaded.
{
"object": "list",
"data": [
{
"name": "Gemma 4 M",
"family": "Gemma",
"sizeCode": "M",
"parameterCountBillions": 4,
"fileSizeMb": 2650,
"supportsTools": true,
"availableForDevice": true,
"downloaded": true,
"selected": true
}
],
"catalog": { "status": "ready", "modelCount": 7 }
}
| Field | Notes |
|---|---|
name |
Exact catalog name — the value a modelName selector matches against |
family / sizeCode |
Coarse selectors, e.g. Gemma + M |
parameterCountBillions |
May be null |
fileSizeMb |
May be null |
supportsTools |
Must be true to be selectable; Automate requires tool calling |
availableForDevice |
false when this device cannot run it (RAM, GPU, OS) |
downloaded |
false means it must be downloaded in the Cephable UI first |
selected |
The model that will serve the next run |
catalog.status |
"loading" while the catalog is still syncing from Cephable; "ready" once available |
catalog.modelCount |
Number of catalog entries |
Poll until catalog.status is "ready" before selecting a model at startup — the catalog syncs asynchronously after app launch.
POST /v1/automate/models/select
Pin a specific on-device model for the current app session. This is a session preference: it overrides the account's synced model choice for the running process without changing the user's saved setting, and is cleared when the server stops.
Request — supply any combination of the three fields; the result must match exactly one catalog entry.
{ "modelName": "Gemma 4 M" }
{ "family": "Gemma", "sizeCode": "M" }
| Field | Type | Notes |
|---|---|---|
modelName |
string? |
Exact catalog name, case-insensitive |
family |
string? |
Case-insensitive family match |
sizeCode |
string? |
Case-insensitive size code, e.g. S, M, L |
An empty object ({}) restores the app's own model selection and unpins yours.
Response
{
"selected": { "name": "Gemma 4 M", "downloaded": true, "selected": true },
"models": [ ]
}
selected is the full model option for the pinned model (or null when unpinning and the app has no preference); models is the refreshed catalog.
Errors
| Status | Cause |
|---|---|
409 |
A run is in flight |
400 |
No catalog match, ambiguous match, model not compatible with the device, model not tool-capable, model not downloaded, or the workflow is not idle |
Switching models terminates a warm worker, so the next run pays a cold start. Select once at integration startup, not per request.
POST /v1/automate/cancel
Stops whatever the assistant is doing — a run you started, a run the user started in the panel, or a run another integration started. This is the one route that is not refused while a run is in flight, and it is safe to call when nothing is running, so a client recovering from a dropped connection can call it unconditionally.
Request (body optional)
{ "force": false }
| Field | Default | Behavior |
|---|---|---|
force: false |
default | Asks the agent loop to unwind. The worker stays warm and the conversation survives, but it only lands between steps — a task already blocked inside a tool call will not notice |
force: true |
Kills the workflow worker outright, exactly as the panel's Stop button does. Always works, at the cost of a cold start on the next run |
Response — always 200
{ "stopped": true, "mode": "cancel", "requestId": "automate-run-…", "workflowStatus": "executing-progress" }
| Field | Notes |
|---|---|
stopped |
false when there was nothing to stop |
mode |
"cancel" · "terminate" · "none" |
requestId |
The run that was stopped, or null |
workflowStatus |
Status observed at the moment of the call |
A run stopped by any route — including the panel's own Stop button — settles the blocking /v1/runs request that started it, rather than leaving it open until its timeout. That request then returns a record with status: "canceled" or "terminated" under HTTP 500.
POST /v1/runs
The native contract. Runs one Automate task and blocks until it reaches a terminal state.
Full treatment of every option, with examples, is in Running Automate tasks. The schema summary:
Request
| Field | Type | Default | Notes |
|---|---|---|---|
prompt |
string |
required | The task. Must be a non-empty string |
taskId |
string? |
— | Your own correlation id, echoed back untouched |
timeoutMs |
number? |
900000 (15 min) |
Minimum 1000. On timeout the run is canceled and the request returns 400 |
thinkingLevel |
"low" | "medium" | "high" | "max" |
user setting | Reasoning budget, applied per run |
additionalWorkflowPrompt |
string? |
— | Advisory preferences appended to the system prompt |
answerContract |
string? |
— | A binding required shape for the closing message. Returns finalAnswer alongside the untouched answer |
include |
object? |
all true |
{ steps?: boolean, trace?: boolean, events?: boolean } — set false to omit a diagnostic block |
restrictToWorkspace |
boolean? |
false |
Confine file and CLI tools to the workspace folder. Can only narrow — a user's own restriction always wins |
continuation |
boolean? |
false |
Continue the previous run's conversation instead of starting fresh |
selectedSkillIds |
string[]? |
— | AI Skills to force into this run. Omit to let Cephable select by relevance |
selectedMcpServerIds |
string[]? |
— | Configured MCP servers to force into this run. Omit for relevance-based selection |
mcpServers |
object[]? |
— | Inline MCP servers for this run only — your own tools, without the user configuring anything. Max 8. See Custom tools |
clientTools |
object[]? |
— | Tools you declare by JSON Schema and execute yourself. The run parks when the agent calls one. Max 32. See Custom tools |
hitlAnswers |
Record<string, string | string[]>? |
— | Pre-supplied answers for request_user_input questions, keyed by field id |
allowDestructiveTools |
boolean? |
false |
Approve destructive-tool requests (delete_path, run_command, move_path) instead of rejecting them |
Response
{
"schemaVersion": 1,
"requestId": "automate-run-9c2e…",
"taskId": "nightly-42",
"status": "completed",
"answer": "…",
"finalAnswer": "…",
"errorCode": "TOOL_TIMEOUT",
"startedAt": "2026-09-14T18:02:11.004Z",
"completedAt": "2026-09-14T18:02:48.771Z",
"durationMs": 37767,
"model": "gemma-4-4b-it-Q4_K_M.gguf",
"appVersion": "4.2.1",
"backend": { "flavorId": "vulkan", "accelerator": "vulkan", "cpuFallback": false },
"steps": [ ],
"trace": [ ],
"usage": { "inputTokens": 5120, "outputTokens": 344, "generationMs": 8345, "ttftMs": 610, "tps": 41.2 },
"events": [ ]
}
| Field | Always present | Notes |
|---|---|---|
schemaVersion |
Yes | Always 1. The reliable signal that a run actually happened |
requestId |
Yes | Server-minted, automate-run-<uuid> |
taskId |
— | Only when you sent one |
status |
Yes | completed · failed · canceled · terminated · awaiting_tool_results |
toolCalls |
— | Only when status is awaiting_tool_results: the caller-executed tool calls to run |
resumeToken |
— | Only when status is awaiting_tool_results: post results to /v1/runs/{resumeToken}/tool-results |
answer |
Yes | The closing message, exactly as a person would have seen it |
finalAnswer |
— | Only when answerContract was set: the text after the last FINAL ANSWER: marker, or the whole answer if no marker was emitted |
errorCode |
— | See workflow error codes |
startedAt / completedAt |
Yes | ISO 8601 |
durationMs |
Yes | Wall clock, including model load and cold start |
model |
Yes | GGUF file name actually loaded, or "unknown" |
appVersion |
Yes | Desktop app version |
backend |
Yes | May be null if the runtime could not be queried |
steps |
unless excluded | Ordered tool calls with args, result summaries, produced paths and content |
trace |
unless excluded | Raw model message list |
usage |
Yes | null when no metrics were emitted |
events |
unless excluded | Every raw channel event. Large — the full step list is rebroadcast on every update, so a research run's stream runs to hundreds of KB |
Status codes
| Status | Body | Meaning |
|---|---|---|
200 |
run record | status === "completed", or awaiting_tool_results — a parked run is a successful exchange |
500 |
run record | The run ran and ended failed, canceled, or terminated. Not a server fault — schemaVersion is still 1 |
400 |
{ error } |
Invalid request, body over 1 MiB, malformed JSON, run timeout, or an initialization failure |
401 |
{ error } |
Bad or missing bearer token |
409 |
{ error } |
A run is already active or preparing |
POST /v1/runs/{resumeToken}/tool-results
Resume a run parked awaiting caller-executed tool results. Deliberately not refused while busy — the run it resumes is the reason the server is busy.
Request
{
"results": [
{ "id": "client_tool_5f2a…", "result": "Order 4471: 2x widget, shipped 2026-09-02" },
{ "id": "client_tool_9b13…", "error": "the orders database is unreachable" }
]
}
| Field | Type | Notes |
|---|---|---|
results |
object[] |
Required. One entry per call you are answering |
results[].id |
string |
Required. The id from the toolCalls entry |
results[].result |
any | What the model sees. A string passes through; anything else is JSON-encoded |
results[].error |
string? |
Report a failure instead. The agent adapts rather than dying |
Results are matched by id, not order. An id that matches nothing pending is ignored, which makes a retried resume safe.
Response — the run's next outcome: another awaiting_tool_results record (an agent loop can need several rounds), or the finished record. Same shape and status codes as /v1/runs, plus:
| Status | Cause |
|---|---|
404 |
No parked run matches that token — it timed out (two minutes per round) or was cancelled |
400 |
Malformed results |
Full treatment in Custom tools.
POST /v1/chat/completions
The OpenAI-compatible contract over the same run engine. Non-streaming; each request executes the complete assistant loop. See OpenAI compatibility for the mapping, the cephable extension object, and the unsupported parameters.
{
"model": "cephable-agent",
"messages": [
{ "role": "system", "content": "Prefer metric units." },
{ "role": "user", "content": "Summarize today's notes file." }
],
"timeout_ms": 600000,
"cephable": {
"include": { "events": false },
"answerContract": "End with FINAL ANSWER: <one sentence>"
}
}
The full native run record is returned in the response's cephable field.
Error shape
Every non-run error uses the same envelope:
{ "error": { "message": "prompt must be a non-empty string", "type": "invalid_request_error" } }
type |
Status | Cause |
|---|---|---|
authentication_error |
401 |
Missing or wrong bearer token |
not_found |
404 |
Unknown path, or the right path with the wrong method |
conflict |
409 |
A run is already active or preparing |
invalid_request_error |
400 |
Validation failure, oversized or malformed body, run timeout, model-selection problem, initialization failure |
Distinguish a failed run from a failed request by checking for schemaVersion === 1 rather than by HTTP status.
Workflow statuses
Reported by /health as workflowStatus and by run records as status.
Ready: idle, and terminated (the worker is gone but nothing is running)
In progress: initializing · planning · thinking · executing-start · executing-progress · executing-complete · executing-org-block · executing-destructive-block · executing-type-focus-block · executing-permission-block · waitingForUser
Terminal for a run: completed · failed · canceled · terminated
Run-record only: awaiting_tool_results — not a workflow status. A run parked awaiting caller-executed tool results, still alive and still holding the inference slot. See Custom tools.
Workflow error codes
Returned as errorCode on a non-completed run.
| Code | Meaning |
|---|---|
MODEL_LOAD_FAILED |
The GGUF could not be loaded |
PLANNING_TIMEOUT |
The model did not produce a plan in time |
TOOL_TIMEOUT |
A tool call exceeded its budget |
EXECUTION_TIMEOUT |
The run exceeded its execution budget |
EXECUTION_FAILED |
A step failed unrecoverably |
CONTEXT_LIMIT_EXCEEDED |
The conversation outgrew contextSize |
UNSUPPORTED_DEVICE |
The device cannot run AI Workflows |
UNSUPPORTED_OS_VERSION |
The OS is below the supported minimum |
DISABLED_BY_POLICY |
An organization policy blocked the run |
SCHEMA_INVALID |
A tool call did not match its schema |
SERVER_LOST |
The llama.cpp server went away mid-run |
CANCELED |
Cancelled by a client, the panel, or a timeout |