# 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](/docs/api-quickstart) for executable requests. ## Base URL and authentication Use `https://api.actual.inc` and send an inference credential from [User > Keys](/user/keys): ```http Authorization: Bearer ac_your_credential_here ``` Console Chat uses your signed-in web session. Device-management authentication is separate from public inference credentials. ## Supported public inference routes | Endpoint | Method | Compatibility | Streaming | | --- | --- | --- | --- | | `/v1/models` | GET | OpenAI-style inventory with Actual extensions | No | | `/v1/chat/completions` | POST | OpenAI Chat Completions | SSE | | `/v1/completions` | POST | Legacy text completions | SSE | | `/v1/responses` | POST | OpenResponses | SSE | | `/v1/messages` | POST | Anthropic Messages | SSE | 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: ```http 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. | Field | Meaning | | --- | --- | | `id` | Public model identifier. May be a display name, or a stable selector when names collide. | | `selector` | Optional stable selector for a particular installed model. | | `display_name` / `canonical_name` | Optional descriptive names. | | `object`, `created`, `owned_by` | Model metadata; `owned_by` is `actual`. | | `loaded` | Whether the model is loaded anywhere in the returned inventory. Missing or false is not readiness. | | `clusters` | Cluster availability, with `cluster_id` and optional per-cluster `loaded`. | 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: ```json { "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. ```json { "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: ```json { "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: ```text 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. ```json { "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: | Field | Use | | --- | --- | | `instructions` | System instructions. | | `max_output_tokens` | Output token limit. | | `stream` | Enable SSE. | | `tools`, `tool_choice` | Tool definitions and selection, when supported by the model and engine. | | `reasoning` | Reasoning settings, including model-supported effort levels. | | `previous_response_id` | Continue a previous response when it is still available. | | `temperature`, `top_p`, `top_k`, `min_p` | Sampling controls supported by the runtime. | | `frequency_penalty`, `presence_penalty`, `repetition_penalty` | Penalty controls. | | `seed`, `stop`, `metadata` | Additional generation and request metadata. | 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: ```json { "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: ```json { "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. | Event | Meaning | | --- | --- | | `response.created` | Response created. | | `response.in_progress` | Processing started. | | `response.output_item.added` | New output item. | | `response.output_text.delta` | Incremental text. | | `response.output_text.done` | Text complete. | | `response.function_call_arguments.delta` | Incremental tool arguments. | | `response.function_call_arguments.done` | Tool arguments complete. | | `response.output_item.done` | Output item complete. | | `response.completed` | Response complete. | | `response.failed` | Response failed. | ## 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. ```json { "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. Console Chat currently accepts text only. See [Vision & Audio](/docs/vision-and-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: ```text 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`. | Status | Typical interpretation | | --- | --- | | 400 | Invalid parameters or unsupported request shape. | | 401 / 403 | Authentication or authorization failed. | | 404 | Unknown route or unavailable target; inspect the body. | | 413 | Prepared request body exceeds the limit. | | 429 | Rate limit or capacity policy; honor retry information when supplied. | | 503 | No eligible serving node or temporary service unavailability. | Refresh inventory after disconnects or model changes. Retry transient failures with backoff; correct credentials, unsupported inputs, and invalid routes before retrying.