Anolis HTTP API Reference (v0)
This document describes the HTTP REST API exposed by anolis-runtime for device discovery, state monitoring, and control.
Overview
- Base URL:
http://127.0.0.1:8080(configurable) - Content-Type:
application/json - API Version:
v0(all endpoints prefixed with/v0)
Authentication
No authentication in v0. The server binds to localhost only by default.
CORS
The server includes CORS headers for browser-based clients:
Access-Control-Allow-Origin: Configurable viahttp.cors_allowed_origins(default:[*])Access-Control-Allow-Methods:GET, POST, OPTIONSAccess-Control-Allow-Headers:Content-Type
This allows the Operator UI (tools/operator-ui/) to connect from any origin by default.
Note: For production/validation, restrict
http.cors_allowed_originsinanolis-runtime.yaml.
Response Format
All responses include a status object:
{
"status": {
"code": "OK",
"message": "ok"
}
// ... endpoint-specific data
}
Status Codes
| Code | HTTP Status | Description |
|---|---|---|
OK |
200 | Success |
INVALID_ARGUMENT |
400 | Bad request or invalid parameters |
NOT_FOUND |
404 | Resource not found |
FAILED_PRECONDITION |
409 | Precondition not met |
UNAVAILABLE |
503 | Provider or device unavailable |
DEADLINE_EXCEEDED |
504 | Request timeout |
INTERNAL |
500 | Internal server error |
Endpoints
GET /v0/runtime/status
Get runtime status, mode, and provider availability summary.
Response:
{
"status": { "code": "OK", "message": "ok" },
"mode": "MANUAL",
"uptime_seconds": 3600,
"polling_interval_ms": 500,
"device_count": 2,
"providers": [
{
"provider_id": "sim0",
"state": "AVAILABLE",
"device_count": 2
}
]
}
Fields:
| Field | Type | Description |
|---|---|---|
mode |
string | Runtime mode: MANUAL, AUTO, IDLE, or FAULT |
uptime_seconds |
integer | Seconds since runtime started |
polling_interval_ms |
integer | State polling interval |
device_count |
integer | Total devices across all providers |
providers |
array | Provider status list |
providers[].state |
string | AVAILABLE or UNAVAILABLE |
/v0/runtime/status intentionally exposes coarse provider availability only.
Use GET /v0/providers/health for restart/backoff
details via the supervision block.
GET /v0/providers/health
Get detailed health, timing, and supervision state for all providers.
Response:
{
"status": { "code": "OK", "message": "ok" },
"providers": [
{
"provider_id": "sim0",
"state": "AVAILABLE",
"lifecycle_state": "RUNNING",
"device_count": 4,
"last_seen_ago_ms": 312,
"uptime_seconds": 47,
"supervision": {
"enabled": true,
"attempt_count": 0,
"max_attempts": 3,
"crash_detected": false,
"circuit_open": false,
"next_restart_in_ms": null
},
"devices": [
{
"device_id": "tempctl0",
"health": "OK",
"last_poll_ms": 1708704000312,
"staleness_ms": 312
}
]
}
]
}
Provider-level fields:
| Field | Type | Description |
|---|---|---|
state |
string | AVAILABLE or UNAVAILABLE |
lifecycle_state |
string | Additive lifecycle signal: RUNNING, RECOVERING, RESTARTING, CIRCUIT_OPEN, or DOWN |
device_count |
integer | Number of devices registered under this provider |
last_seen_ago_ms |
integer or null | Milliseconds since the last confirmed healthy heartbeat. Counts up while UNAVAILABLE. null before the very first healthy poll. |
uptime_seconds |
integer | Seconds since the first confirmed healthy heartbeat of the current process instance. 0 when UNAVAILABLE or before first poll. |
supervision |
object | Supervision state block ā always present, never null (see below). |
devices |
array | Per-device health entries for this provider. |
lifecycle_state values:
| Value | Meaning |
|---|---|
RUNNING |
Provider is available with no active restart streak. |
RECOVERING |
Provider is available but still inside the post-restart stability window (attempt_count > 0). |
RESTARTING |
Provider is unavailable and supervised restart/backoff is active. |
CIRCUIT_OPEN |
Provider is unavailable and restart attempts are exhausted (supervision.circuit_open: true). |
DOWN |
Provider is unavailable without active supervised restart metadata. |
supervision object fields:
| Field | Type | Description |
|---|---|---|
enabled |
boolean | true if a restart policy is configured for this provider |
attempt_count |
integer | Current consecutive restart attempts since last recovery. Resets to 0 on successful recovery. |
max_attempts |
integer | Maximum restart attempts before circuit breaker opens (from config) |
crash_detected |
boolean | true while a crash is actively being processed (cleared after restart attempt begins) |
circuit_open |
boolean | true when max_attempts has been exceeded ā no further restarts will be attempted |
next_restart_in_ms |
integer or null | Countdown to next restart attempt (see semantics table below) |
next_restart_in_ms semantics:
| State | Value | How to distinguish |
|---|---|---|
| Healthy ā no crash history | null |
circuit_open: false, attempt_count: 0 |
| In backoff window ā restart pending | positive integer (ms remaining) | attempt_count > 0, circuit_open: false |
| Restart eligible now | 0 |
Backoff elapsed; restart attempt imminent |
| Circuit open ā no further restarts ever | null |
circuit_open: true disambiguates from healthy null |
Note:
nullappears in both the healthy state and the circuit-open state. Always readcircuit_opento distinguish them.
Device health values:
health |
Description |
|---|---|
OK |
State polled within the last 2 seconds |
WARNING |
State is 2ā5 seconds old |
STALE |
State is older than 5 seconds |
UNAVAILABLE |
Provider is UNAVAILABLE; no polling occurring |
UNKNOWN |
No state data for this device yet |
GET /v0/mode
Get current automation mode and manual gating policy.
Response:
{
"status": { "code": "OK", "message": "ok" },
"mode": "MANUAL",
"policy": "BLOCK"
}
POST /v0/mode
Set automation mode.
Request:
{ "mode": "AUTO" }
Response:
{ "status": { "code": "OK", "message": "ok" }, "mode": "AUTO" }
GET /v0/parameters
List all runtime parameters with current values and constraints.
Response:
{
"status": { "code": "OK", "message": "ok" },
"parameters": [
{
"name": "temp_setpoint",
"type": "double",
"value": 25.0,
"min": 10.0,
"max": 50.0
}
]
}
POST /v0/parameters
Update a runtime parameter (validated against constraints).
Request:
{ "name": "temp_setpoint", "value": 30.0 }
Response:
{
"status": { "code": "OK", "message": "ok" },
"parameter": { "name": "temp_setpoint", "value": 30.0 }
}
GET /v0/automation/tree
Get the loaded behavior tree XML content.
Response:
{
"status": { "code": "OK", "message": "ok" },
"tree": "<root main_tree_to_execute=\"MainTree\">\n <BehaviorTree ID=\"MainTree\">...</BehaviorTree>\n</root>"
}
Error Responses:
UNAVAILABLE- Automation layer not enabledNOT_FOUND- No behavior tree loadedINTERNAL- Failed to read behavior tree file
GET /v0/devices
List all discovered devices.
Response:
{
"status": { "code": "OK", "message": "ok" },
"devices": [
{
"provider_id": "sim0",
"device_id": "tempctl0",
"type": "tempctl"
},
{
"provider_id": "sim0",
"device_id": "motorctl0",
"type": "motorctl"
}
]
}
Fields:
| Field | Type | Description |
|---|---|---|
devices[].provider_id |
string | Provider that owns this device |
devices[].device_id |
string | Unique device identifier (within provider) |
devices[].type |
string | Device type identifier |
GET /v0/devices/{provider_id}/{device_id}/capabilities
Get device capabilities (signals and functions).
Response:
{
"status": { "code": "OK", "message": "ok" },
"provider_id": "sim0",
"device_id": "tempctl0",
"capabilities": {
"signals": [
{
"signal_id": "tc1_temp",
"label": "TC1 Temperature",
"value_type": "double"
},
{
"signal_id": "relay1_state",
"label": "Relay 1 State",
"value_type": "bool"
},
{
"signal_id": "control_mode",
"label": "Control Mode",
"value_type": "string"
}
],
"functions": [
{
"function_id": 1,
"name": "set_mode",
"label": "Set control mode: open or closed",
"args": {
"mode": {}
}
},
{
"function_id": 2,
"name": "set_setpoint",
"label": "Set closed-loop setpoint (C)",
"args": {
"value": {}
}
}
]
}
}
Signal Fields:
| Field | Type | Description |
|---|---|---|
signal_id |
string | Signal identifier |
label |
string | Human-readable label |
value_type |
string | Value type: double, int64, uint64, bool, string, bytes |
Function Fields:
| Field | Type | Description |
|---|---|---|
function_id |
integer | Numeric function ID for calls |
name |
string | Function name |
label |
string | Human-readable description |
args |
object | Argument names (values are type hints) |
GET /v0/state
Get latest cached state for all devices.
Response:
{
"status": { "code": "OK", "message": "ok" },
"generated_at_epoch_ms": 1730000000000,
"devices": [
{
"provider_id": "sim0",
"device_id": "tempctl0",
"quality": "OK",
"values": [
{
"signal_id": "tc1_temp",
"value": { "type": "double", "double": 23.5 },
"quality": "OK",
"timestamp_epoch_ms": 1730000000000,
"age_ms": 150
},
{
"signal_id": "relay1_state",
"value": { "type": "bool", "bool": false },
"quality": "OK",
"timestamp_epoch_ms": 1730000000000,
"age_ms": 150
}
]
}
]
}
Device Fields:
| Field | Type | Description |
|---|---|---|
quality |
string | Overall device quality (worst of signals) |
Value Fields:
| Field | Type | Description |
|---|---|---|
signal_id |
string | Signal identifier |
value |
object | Typed value (see Value Types below) |
quality |
string | Signal quality: OK, STALE, UNAVAILABLE, FAULT |
timestamp_epoch_ms |
integer | When value was polled (Unix ms) |
age_ms |
integer | Milliseconds since poll |
GET /v0/state/{provider_id}/{device_id}
Get state for a single device.
Response:
{
"status": { "code": "OK", "message": "ok" },
"generated_at_epoch_ms": 1730000000000,
"provider_id": "sim0",
"device_id": "motorctl0",
"quality": "OK",
"values": [
{
"signal_id": "motor1_duty",
"value": { "type": "double", "double": 0.75 },
"quality": "OK",
"timestamp_epoch_ms": 1730000000000,
"age_ms": 50
}
]
}
POST /v0/call
Execute a device function.
Request:
{
"provider_id": "sim0",
"device_id": "motorctl0",
"function_id": 10,
"args": {
"motor_index": { "type": "int64", "int64": 1 },
"duty": { "type": "double", "double": 0.75 }
}
}
Request Fields:
| Field | Type | Required | Description |
|---|---|---|---|
provider_id |
string | Yes | Target provider |
device_id |
string | Yes | Target device |
function_id |
integer | Yes | Function ID from capabilities |
args |
object | No | Function arguments (typed values) |
Response (Success):
{
"status": { "code": "OK", "message": "ok" },
"provider_id": "sim0",
"device_id": "motorctl0",
"function_id": 10,
"post_call_poll_triggered": true
}
Response (Error):
{
"status": {
"code": "INVALID_ARGUMENT",
"message": "Provider returned error: motor_index must be 1 or 2"
}
}
Value Types
All values use a typed JSON encoding:
| Type | JSON Format | Example |
|---|---|---|
double |
{"type": "double", "double": 1.23} |
Temperature, duty cycle |
int64 |
{"type": "int64", "int64": -42} |
Signed integers |
uint64 |
{"type": "uint64", "uint64": 12345} |
Counters, timestamps |
bool |
{"type": "bool", "bool": true} |
Relay states, flags |
string |
{"type": "string", "string": "open"} |
Mode, status text |
bytes |
{"type": "bytes", "base64": "AAECAw=="} |
Binary data (base64) |
Quality Values
| Quality | Description |
|---|---|
OK |
Fresh data, recently polled |
STALE |
Provider reachable but data is old |
UNAVAILABLE |
Provider or device unreachable |
FAULT |
Device-reported fault condition |
Device-level quality is the worst-case of its signal qualities.
Error Responses
All errors follow the same format:
{
"status": {
"code": "NOT_FOUND",
"message": "Device not found: sim0/nonexistent"
}
}
Common Errors
| Scenario | HTTP | Code |
|---|---|---|
| Unknown route | 404 | NOT_FOUND |
| Unknown device | 404 | NOT_FOUND |
| Invalid JSON body | 400 | INVALID_ARGUMENT |
| Missing required field | 400 | INVALID_ARGUMENT |
| Provider unavailable | 503 | UNAVAILABLE |
| Call timeout | 504 | DEADLINE_EXCEEDED |
| Internal error | 500 | INTERNAL |
Configuration
HTTP server is configured in anolis-runtime.yaml:
http:
enabled: true # Enable HTTP server (default: true)
bind: 127.0.0.1 # Bind address (default: 127.0.0.1)
port: 8080 # Port (default: 8080)
Curl Examples
List devices
curl -s http://127.0.0.1:8080/v0/devices | jq
Get device capabilities
curl -s http://127.0.0.1:8080/v0/devices/sim0/tempctl0/capabilities | jq
Get all state
curl -s http://127.0.0.1:8080/v0/state | jq
Get single device state
curl -s http://127.0.0.1:8080/v0/state/sim0/motorctl0 | jq
Set motor duty
curl -s -X POST http://127.0.0.1:8080/v0/call \
-H "Content-Type: application/json" \
-d '{
"provider_id": "sim0",
"device_id": "motorctl0",
"function_id": 10,
"args": {
"motor_index": {"type": "int64", "int64": 1},
"duty": {"type": "double", "double": 0.75}
}
}' | jq
Set temperature setpoint
curl -s -X POST http://127.0.0.1:8080/v0/call \
-H "Content-Type: application/json" \
-d '{
"provider_id": "sim0",
"device_id": "tempctl0",
"function_id": 2,
"args": {
"value": {"type": "double", "double": 50.0}
}
}' | jq
Get runtime status
curl -s http://127.0.0.1:8080/v0/runtime/status | jq
Get provider health and supervision state
curl -s http://127.0.0.1:8080/v0/providers/health | jq