API Reference
The public relay exposes model inventory and HTTP inference, with Server-Sent Events (SSE) for streaming. Protocol compatibility depends on the serving client, inference engine, and model capabilities.
Follow API Quickstart for executable requests.
Base URL and authentication
Use https://api.actual.inc and send an inference credential from User > Keys:
Authorization: Bearer ac_your_credential_here
Chat on actual.inc uses your signed-in web session. Device-management authentication is separate from public inference credentials.
Supported public inference routes
The current relay does not serve public cluster-listing, model-loading, or legacy session-WebSocket endpoints. Use actual clusters --format json for the cluster roster, and the CLI or website for model installation and loading.
Cluster targeting and readiness
Send X-Cluster-ID to scope inventory or inference to a specific cluster:
X-Cluster-ID: cl_example
Discover cluster IDs from clusters[].cluster_id in model inventory or the authenticated CLI. Without this header, inventory aggregates your account's clusters. Inference filters eligible online nodes by the loaded model and required protocol or media capabilities, then selects among them. It does not select a fixed first cluster.
Installed is not loaded. A model must be resident and ready on an eligible node before inference. Cluster connectivity also does not establish that a model executes across multiple machines.
GET /v1/models
Returns models advertised by online devices, including installed models available to load and currently loaded models.
Use a returned id or selector as the request's model. For pinned inference, check loading on that specific cluster instead of using the aggregate flag.
Illustrative response:
{
"object": "list",
"data": [{
"id": "Gemma 4 12B IT",
"object": "model",
"created": 1700000000,
"owned_by": "actual",
"selector": "mdl_a81e2d366803a648",
"display_name": "Gemma 4 12B IT",
"loaded": true,
"clusters": [
{"cluster_id": "cl_loaded", "loaded": true},
{"cluster_id": "cl_installed", "loaded": false}
]
}]
}
POST /v1/chat/completions
Provide a model identifier and an array of messages. This example uses an illustrative model selector; replace it with inventory output.
{
"model": "mdl_a81e2d366803a648",
"messages": [
{"role": "system", "content": "Answer concisely."},
{"role": "user", "content": "Say hello."}
],
"max_tokens": 128,
"stream": false
}
Common optional sampling fields include temperature, top_p, frequency_penalty, presence_penalty, and stop. Supported fields, bounds, defaults, reasoning levels, and tool behavior depend on the serving engine and model; the website's Chat defaults are not universal API defaults.
A successful non-streaming response includes choices[].message, finish_reason, and usage when available:
{
"id": "chatcmpl-example",
"object": "chat.completion",
"created": 1700000000,
"model": "mdl_a81e2d366803a648",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Hello!"},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 12, "completion_tokens": 3, "total_tokens": 15}
}
With stream: true, consume SSE data events and accumulate deltas. A simplified text stream is:
data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"Hello!"},"finish_reason":null}]}
data: [DONE]
Handle tool-call deltas and stream errors separately from text. A chunk arriving is not proof that generation completed successfully.
POST /v1/completions
The legacy text-completion route accepts model, prompt, and optional generation controls such as max_tokens, temperature, top_p, stop, and stream. Prefer Chat Completions or Responses for new conversational integrations.
{
"model": "mdl_a81e2d366803a648",
"prompt": "Complete this sentence: The computer",
"max_tokens": 64,
"stream": false
}
Non-streaming completions return text in choices[].text, with finish reason and usage where available.
POST /v1/responses
Provide model and input, which can be text or an array of input items. Common fields include:
Do not assume every runtime supports every optional field. A previous response may become unavailable after restarting or changing the serving computer. Keep your conversation history so the application can recover if continuation fails.
Example with a function tool:
{
"model": "mdl_a81e2d366803a648",
"input": "What is the temperature in Boston?",
"tools": [{
"type": "function",
"name": "get_temperature",
"description": "Get the current temperature for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"max_output_tokens": 256
}
Your application validates and executes any requested function, then returns a function_call_output using the returned call_id. The inference endpoint does not execute your application functions for you.
A text response has an output array containing message items:
{
"id": "resp-example",
"object": "response",
"status": "completed",
"output": [{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "Hello!"}]
}],
"error": null
}
Streaming uses named events. Check the terminal event and response status.
POST /v1/messages
The Anthropic-compatible route accepts model, messages, max_tokens, and optional system, tools, and stream. Use the same bearer authentication and cluster header as the other public routes.
{
"model": "mdl_a81e2d366803a648",
"max_tokens": 128,
"messages": [{"role": "user", "content": "Say hello."}],
"stream": false
}
Responses use the Anthropic-style message shape; streaming uses Anthropic-style SSE events. Client compatibility still depends on the selected model's capabilities and the application's provider configuration.
Images, audio, and request size
Use a model and serving runtime that advertise the required input capabilities. Chat on actual.inc currently accepts text only. See Vision & Audio for application and file guidance.
The current standard request limit is 16 MiB for the complete prepared body, including encoded attachments. A smaller source file can become a larger encoded request.
Errors
Inspect both HTTP status and content type. Errors are not uniformly JSON. For example, a missing bearer credential can produce a 401 plain-text body:
missing_or_malformed_credential
A no-node condition can produce 503 with plain text no node for user. Other failures return structured JSON. Preserve the received diagnostic instead of assuming legacy error codes such as invalid_api_key or user_offline.
Refresh inventory after disconnects or model changes. Retry transient failures with backoff; correct credentials, unsupported inputs, and invalid routes before retrying.