API Reference
Base URL
http://127.0.0.1:8420
Override with the api_host and api_port config options.
Authentication
Localhost requests (127.0.0.1) bypass authentication. When the daemon binds to 0.0.0.0 (LAN access), a token is auto-generated and stored at ~/.config/casectl/.api-token.
Provide the token via any of:
# Bearer header
curl -H "Authorization: Bearer <token>" http://pi:8420/api/health
# Query parameter
curl http://pi:8420/api/health?token=<token>
# Cookie (set by the web dashboard login)
View your token: casectl token
Health & Config
GET /api/health
Daemon health, uptime, version, and plugin status.
{
"status": "running",
"uptime": 3600,
"version": "0.2.0",
"plugins": [
{"name": "fan-control", "version": "0.2.0", "status": "healthy", "description": "..."}
]
}
GET /api/config/{section}
Get a configuration section. Sections: fan, led, oled, service, alerts, mqtt, automation.
curl http://localhost:8420/api/config/fan
{
"mode": 0,
"manual_duty": [75, 75, 75],
"thresholds": {"low_temp": 30, "high_temp": 50, "low_speed": 75, "mid_speed": 125, "high_speed": 175}
}
Sensitive fields (smtp_password, ntfy_token, mqtt.password) are redacted in responses.
PATCH /api/config
Update a configuration section.
curl -X PATCH http://localhost:8420/api/config \
-H "Content-Type: application/json" \
-d '{"section": "fan", "values": {"mode": 2}}'
Fan Control
GET /api/plugins/fan-control/status
{
"mode": "follow_temp",
"duty": [140, 140, 140],
"rpm": [2400, 1800, 1800],
"temp": 46.3,
"degraded": false
}
PUT /api/plugins/fan-control/mode
Set the fan operating mode. Accepts integer or string name.
curl -X PUT http://localhost:8420/api/plugins/fan-control/mode \
-H "Content-Type: application/json" \
-d '{"mode": "follow-temp"}'
| Mode | Value | Description |
|---|---|---|
| follow-temp | 0 | Automatic PWM based on CPU temperature curve |
| follow-rpi | 1 | Mirror the Raspberry Pi’s own fan PWM signal |
| manual | 2 | Fixed duty cycle |
| custom | 3 | Multi-point fan curve (see fan curve config) |
| off | 4 | Fans disabled |
PUT /api/plugins/fan-control/speed
Set fan duty cycle (manual mode). Each value is 0-100%.
curl -X PUT http://localhost:8420/api/plugins/fan-control/speed \
-H "Content-Type: application/json" \
-d '{"duty": [75, 75, 75]}'
LED Control
GET /api/plugins/led-control/status
{
"mode": "manual",
"color": {"red": 138, "green": 170, "blue": 196},
"hex": "#8AAAC4",
"color_name": "arctic-steel",
"degraded": false
}
PUT /api/plugins/led-control/mode
| Mode | Value | Description |
|---|---|---|
| rainbow | 0 | Automatic colour cycling (STM32 firmware) |
| breathing | 1 | Pulsing brightness effect (STM32 firmware) |
| follow-temp | 2 | Colour follows CPU temperature |
| manual | 3 | Fixed colour |
| custom | 4 | Reserved |
| off | 5 | LEDs disabled |
curl -X PUT http://localhost:8420/api/plugins/led-control/mode \
-H "Content-Type: application/json" \
-d '{"mode": "off"}'
PUT /api/plugins/led-control/color
Set RGB colour (0-255 each). Switches to manual mode.
curl -X PUT http://localhost:8420/api/plugins/led-control/color \
-H "Content-Type: application/json" \
-d '{"red": 255, "green": 0, "blue": 128}'
OLED Display
GET /api/plugins/oled-display/status
{
"current_screen": 1,
"screen_names": ["clock", "metrics", "temperature", "fan_duty"],
"screens_enabled": [true, true, true, true],
"rotation": 180,
"degraded": false
}
PUT /api/plugins/oled-display/screen
Enable or disable a screen in the rotation cycle.
curl -X PUT http://localhost:8420/api/plugins/oled-display/screen \
-H "Content-Type: application/json" \
-d '{"index": 2, "enabled": false}'
PUT /api/plugins/oled-display/rotation
Set display rotation. Valid values: 0 or 180.
curl -X PUT http://localhost:8420/api/plugins/oled-display/rotation \
-H "Content-Type: application/json" \
-d '{"rotation": 180}'
PUT /api/plugins/oled-display/power
Enable or disable the entire OLED display.
curl -X PUT http://localhost:8420/api/plugins/oled-display/power \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
PUT /api/plugins/oled-display/content
Configure per-screen display settings (time format, date format, display time).
curl -X PUT http://localhost:8420/api/plugins/oled-display/content \
-H "Content-Type: application/json" \
-d '{"index": 0, "display_time": 10.0, "time_format": 1}'
System Monitor
GET /api/plugins/system-monitor/status
{
"cpu_percent": 12.5,
"memory_percent": 24.6,
"disk_percent": 6.5,
"cpu_temp": 46.3,
"case_temp": 30.0,
"ip_address": "192.168.0.238",
"fan_duty": [140, 140, 140],
"motor_speed": [2400, 1800, 1800],
"date": "2026-03-28",
"weekday": "Saturday",
"time": "14:30:00",
"rpi_fan_duty": 128
}
Prometheus
GET /api/plugins/prometheus/metrics
Returns metrics in Prometheus text exposition format.
# HELP casectl_cpu_temp_celsius CPU die temperature in degrees Celsius.
# TYPE casectl_cpu_temp_celsius gauge
casectl_cpu_temp_celsius 46.3
# HELP casectl_case_temp_celsius Case / ambient temperature in degrees Celsius.
# TYPE casectl_case_temp_celsius gauge
casectl_case_temp_celsius 30.0
# HELP casectl_cpu_usage_ratio CPU utilisation as a ratio (0.0 to 1.0).
# TYPE casectl_cpu_usage_ratio gauge
casectl_cpu_usage_ratio 0.125
# HELP casectl_memory_usage_ratio Memory utilisation as a ratio.
# TYPE casectl_memory_usage_ratio gauge
casectl_memory_usage_ratio 0.246
# HELP casectl_disk_usage_ratio Root disk utilisation as a ratio.
# TYPE casectl_disk_usage_ratio gauge
casectl_disk_usage_ratio 0.065
# HELP casectl_fan_duty_ratio Fan PWM duty cycle as a ratio.
# TYPE casectl_fan_duty_ratio gauge
casectl_fan_duty_ratio{channel="0"} 0.549
casectl_fan_duty_ratio{channel="1"} 0.549
casectl_fan_duty_ratio{channel="2"} 0.549
Configure your Prometheus scrape target:
scrape_configs:
- job_name: casectl
static_configs:
- targets: ['pi-hostname:8420']
metrics_path: /api/plugins/prometheus/metrics
MQTT
GET /api/plugins/mqtt/status
MQTT connection status, message counters, and Home Assistant device count.
{
"connected": true,
"broker": "localhost:1883",
"devices": 1,
"messages_published": 420,
"messages_received": 12,
"errors": 0
}
GET /api/plugins/mqtt/connection
Detailed connection information including reconnect statistics.
GET /api/plugins/mqtt/devices
List of Home Assistant entities registered via MQTT auto-discovery.
Automation
GET /api/plugins/automation/status
Automation engine status and rule summary.
GET /api/plugins/automation/rules
List all configured automation rules with their enabled state and last-fired time.
POST /api/plugins/automation/reload
Reload automation rules from config without restarting the daemon.
Alerting
GET /api/plugins/alerting/status
Alerting status including active channels, cooldown state, and dispatch statistics.
POST /api/plugins/alerting/test
Send a test alert through all configured channels.
Server-Sent Events (SSE)
GET /api/sse
Real-time server-sent events for live dashboard updates. Preferred over WebSocket for one-way streaming.
const source = new EventSource("http://localhost:8420/api/sse");
source.addEventListener("refresh:fan", (e) => console.log(JSON.parse(e.data)));
source.addEventListener("refresh:led", (e) => console.log(JSON.parse(e.data)));
source.addEventListener("refresh:monitor", (e) => console.log(JSON.parse(e.data)));
Event types: refresh:fan, refresh:led, refresh:oled, refresh:monitor, refresh:all.
- Max 20 concurrent SSE connections
- Heartbeat keepalive every 15 seconds
- Automatic backpressure handling (oldest events dropped if client is slow)
WebSocket
/api/ws
Bidirectional WebSocket for real-time events and commands.
Connecting:
const ws = new WebSocket("ws://localhost:8420/api/ws");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
When the daemon is bound to a non-localhost address, authentication is required via query parameter: ws://host:8420/api/ws?token=<token>.
Receiving events:
{"type": "metrics_updated", "data": {"cpu_temp": 46.3, "cpu_percent": 12.5, ...}}
Sending commands:
{"command": "set_fan_mode", "mode": "manual"}
{"command": "set_fan_speed", "duty": [75, 75, 75]}
{"command": "set_led_mode", "mode": "rainbow"}
{"command": "set_led_color", "red": 255, "green": 0, "blue": 128}
{"command": "set_oled_screen", "index": 0, "enabled": true}
{"command": "set_oled_rotation", "rotation": 180}
{"command": "set_oled_power", "enabled": true}
{"command": "set_oled_content", "index": 0, "display_time": 10.0}
- Max 10 concurrent WebSocket connections (excess closed with code 1008)
metrics_updatedevents arrive every ~2 seconds