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.


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}