REST API reference
Full /api/v1 HTTP reference for the Hypercolor daemon: the JSON envelope, every route group, and the concurrency model.
The Hypercolor daemon serves a REST API over /api/v1 on port 9420 by default. Every route group below is enumerated from the daemon’s own router (build_router() in crates/hypercolor-daemon/src/api/mod.rs), so this page is the contract, not a curated subset. The same daemon also speaks WebSocket, the CLI, and an MCP server; this page covers HTTP only.
🔗Base URL and surfaces 🎯
http://localhost:9420Three paths sit outside the /api/v1 tree:
| Path | Purpose |
|---|---|
/health | Liveness check, no auth, returns 200 OK when the daemon is up. |
/preview | Standalone canvas-preview HTML page. |
/mcp | MCP server (Streamable HTTP), mounted only when mcp.enabled is true. |
Everything else lives under /api/v1. Axum 0.8 path parameters use brace syntax, so a device route is /api/v1/devices/{id}, not :id.
🔗Response envelope
Every JSON response, success or error, carries a meta block. Success responses put the payload under data; errors put it under error. The two keys never both appear.
{
"data": {},
"meta": {
"api_version": "1.0",
"request_id": "req_019b1f9a-3f4b-7c8d-a2e1-91b4c0d86a25",
"timestamp": "2026-06-25T18:03:11.482Z"
}
}The meta fields are fixed by the daemon:
| Field | Shape | Notes |
|---|---|---|
api_version | string "1.0" | The literal envelope version. It is unrelated to the v1 URL segment and never reads "v1". |
request_id | string req_<uuid-v7> | A req_ prefix plus a time-ordered UUID v7. Quote it when filing a bug or correlating logs. |
timestamp | ISO 8601 UTC | Millisecond precision with a trailing Z. |
Error bodies replace data with error:
{
"error": {
"code": "validation_error",
"message": "brightness must be between 0.0 and 1.0",
"details": null
},
"meta": {
"api_version": "1.0",
"request_id": "req_019b1f9a-3f4b-7c8d-a2e1-91b4c0d86a25",
"timestamp": "2026-06-25T18:03:11.482Z"
}
}The code is a snake_case string that maps to an HTTP status. The full set:
code | HTTP status |
|---|---|
bad_request | 400 |
unauthorized | 401 |
forbidden | 403 |
not_found | 404 |
conflict | 409 |
payload_too_large | 413 |
unsupported_media_type | 415 |
validation_error | 422 |
rate_limited | 429 |
internal_error | 500 |
validation_error is 422 Unprocessable Entity, not 400. A well-formed request that fails a business rule (out-of-range brightness, an effect that isn’t runnable) lands here, while a structurally malformed request is bad_request / 400.
🔗Authentication
Loopback clients are exempt from API keys, which is why the local CLI, TUI, and web UI work with no configuration. When you bind the daemon to a non-loopback address or configure a key, send it as a Bearer token:
Authorization: Bearer <your-api-key>There are two keys: HYPERCOLOR_API_KEY grants control (writes), and HYPERCOLOR_READ_API_KEY grants read-only access. CORS allows loopback origins unconditionally; configured cors_origins are only honored once API auth is enabled. The auth and rate-limiting model is documented in full on the auth and security overview.
🔗Concurrency: revisions and If-Match
Scene-zone structural edits use optimistic concurrency. A GET on a scene’s zones returns a groups_revision and an ETag header carrying the same revision. Send that value back as If-Match on the mutating request. If the revision is stale, the daemon rejects the write with 412 Precondition Failed rather than clobbering a concurrent edit. The Studio zone editor relies on this to stay coherent across multiple clients.
🔗System
/healthLiveness check. Returns 200 OK when the daemon is running. No authentication, no envelope. Use this in your reconnect loop and readiness probes.
/api/v1/statusAggregate system status: the running effect, connected device count, audio availability, global brightness, and live render-loop timing.
Response:
{
"data": {
"running": true,
"version": "0.1.0",
"device_count": 3,
"effect_count": 59,
"active_effect": "borealis",
"global_brightness": 85,
"audio_available": true,
"render_loop": {
"state": "running",
"target_fps": 60,
"capacity_fps": 60.0,
"delivered_fps": 59.8,
"actual_fps": 60.0
}
},
"meta": {
"api_version": "1.0",
"request_id": "req_019b1f9a-3f4b-7c8d-a2e1-91b4c0d86a25",
"timestamp": "2026-06-25T18:03:11.482Z"
}
}effect_count reflects whatever the registry holds at request time (native built-ins plus discovered HTML effects); treat it as live, not a fixed product number.
/api/v1/serverStable server identity: instance ID, instance name, and version. This is the same identity advertised over discovery.
/api/v1/system/sensorsLatest hardware sensor snapshot: CPU temperature, GPU load, RAM usage, and raw component readings. These feed sensor-bound effect controls.
/api/v1/system/sensors/{label}A single named sensor reading. Common labels: cpu_temp, gpu_load, ram_used.
🔗Effects

/api/v1/effectsList the effect catalog. Returns data.items (effect summaries) plus data.pagination. Supports the standard offset / limit query params.
Response:
{
"data": {
"items": [
{
"id": "borealis",
"name": "Borealis",
"description": "Aurora borealis with domain-warped fBm noise",
"author": "Hypercolor",
"category": "ambient",
"source": "html",
"runnable": true,
"tags": ["ambient", "shader"],
"version": "1.0.0",
"audio_reactive": false
}
],
"pagination": {
"offset": 0,
"limit": 50,
"total": 59,
"has_more": false
}
},
"meta": {
"api_version": "1.0",
"request_id": "req_019b1f9a-3f4b-7c8d-a2e1-91b4c0d86a25",
"timestamp": "2026-06-25T18:03:11.482Z"
}
}The catalog combines around a dozen native Rust built-ins with the HTML/GLSL effects discovered on disk. Don’t hardcode the count; read pagination.total.
/api/v1/effects/{id}Full detail for one effect, including its control definitions (types, ranges, defaults). The controls array is what a UI renders into sliders, color pickers, and dropdowns.
/api/v1/effects/{id}/applyApply an effect to the active output. Optionally override control defaults.
Request body (optional):
{
"controls": {
"speed": 7,
"palette": "SilkCircuit"
}
}Response: the applied effect, the resolved control values, any layout binding, the resolved transition (cut, 0 today), and a warnings array.
/api/v1/effects/activeThe currently active effect and its live control values.
/api/v1/effects/current/controlsPatch controls on the running effect. Changes take effect on the next frame. Note the path segment is current, not active.
Request body:
{
"controls": {
"speed": 3,
"intensity": 90
}
}/api/v1/effects/current/controls/{name}/bindingBind one named control on the running effect to an input source (audio band, sensor reading, etc.) so it modulates live instead of holding a fixed value.
/api/v1/effects/current/resetReset every control on the running effect back to its default.
/api/v1/effects/{id}/controlsPatch controls on a specific effect by ID, whether or not it is the active one.
/api/v1/effects/stopStop the running effect. Output goes dark.
/api/v1/effects/rescanRescan the effects directory and pick up newly built effects without restarting the daemon. Call this after shipping an effect from the SDK.
/api/v1/effects/installInstall an effect from an uploaded file via multipart form upload, so a freshly built HTML bundle reaches the library without a manual file copy.
/api/v1/effects/{id}/coverCover image for one effect.
/api/v1/effects/active/coverCover image for the active effect.
/api/v1/effects/{id}/layoutGet the layout bound to an effect.
/api/v1/effects/{id}/layoutBind an effect to a spatial layout.
/api/v1/effects/{id}/layoutClear an effect’s layout binding.
Effect screenshots are served statically under /api/v1/effects/screenshots/... from the bundled screenshot root.
🔗Devices

/api/v1/devicesList discovered and connected devices. Returns data.items plus data.pagination.
Response:
{
"data": {
"items": [
{
"id": "razer-blackwidow-v4-001",
"layout_device_id": "razer-blackwidow-v4-001",
"name": "Razer BlackWidow V4",
"status": "connected",
"brightness": 100,
"total_leds": 126,
"zones": []
}
],
"pagination": {
"offset": 0,
"limit": 50,
"total": 1,
"has_more": false
}
},
"meta": {
"api_version": "1.0",
"request_id": "req_019b1f9a-3f4b-7c8d-a2e1-91b4c0d86a25",
"timestamp": "2026-06-25T18:03:11.482Z"
}
}/api/v1/devices/{id}Full detail for one device: zones, LED layout, firmware version, attachment configuration.
/api/v1/devices/{id}Update device settings (name, brightness, zone assignments).
/api/v1/devices/{id}Remove a device from tracking.
/api/v1/devices/discoverTrigger a discovery scan across every backend. Returns newly found devices.
/api/v1/devices/{id}/pairInitiate pairing for a device that requires authentication (Hue link button, Nanoleaf hold-to-pair token). This is the credential path for network devices; see the per-vendor hardware guides for the timed pairing windows.
/api/v1/devices/{id}/pairForget a device’s stored pairing credentials.
/api/v1/devices/{id}/identifyFlash a device’s LEDs so you can spot it physically.
/api/v1/devices/{id}/zones/{zone_id}/identifyFlash one zone on a device to identify it.
/api/v1/devices/{id}/attachments/{slot_id}/identifyFlash one attachment slot’s LEDs to identify it.
/api/v1/devices/{id}/controlsControl surface for a device: fields, types, and current values.
/api/v1/devices/{id}/attachmentsAttachment configuration for a device.
/api/v1/devices/{id}/attachmentsUpdate a device’s attachment configuration.
/api/v1/devices/{id}/attachmentsClear a device’s attachment configuration.
/api/v1/devices/{id}/attachments/previewPreview attachment placement without persisting it.
/api/v1/devices/{id}/logical-devicesList logical-device segments carved out of one physical device.
/api/v1/devices/{id}/logical-devicesCreate a logical-device segment on a physical device.
/api/v1/devices/metricsPer-device output telemetry snapshot: frame counts, errors, latency.
The router also exposes /api/v1/devices/debug/queues and /api/v1/devices/debug/routing for inspecting output queue and routing state while debugging.
🔗Logical devices
Logical devices are user-defined LED-range segments carved out of a physical device so one strip can act as several addressable units.
/api/v1/logical-devicesList every logical-device segment across all physical devices.
/api/v1/logical-devices/{id}Get one logical-device segment.
/api/v1/logical-devices/{id}Update a logical-device segment.
/api/v1/logical-devices/{id}Delete a logical-device segment.
🔗Drivers
/api/v1/driversList registered driver modules with their ID, name, and connection state.
/api/v1/drivers/{id}/configConfiguration for one driver module.
/api/v1/drivers/{id}/controlsControl surface for one driver module: fields, types, current values.
🔗Displays and faces
Display devices are physical screens (AIO LCD modules, Ableton Push 2) that show full-screen HTML faces. See display faces for the authoring contract.
/api/v1/displaysList connected display devices.
/api/v1/displays/{id}/preview.jpgA JPEG preview frame from a display device. Live frame streaming runs over the display_preview WebSocket channel.
/api/v1/displays/{id}/faceThe active face configuration on a display device.
/api/v1/displays/{id}/faceSet the face effect on a display device. Binds an HTML effect to the device in the active scene.
/api/v1/displays/{id}/faceRemove the face assignment from a display device.
/api/v1/displays/{id}/face/controlsPatch control values on a display’s active face.
/api/v1/displays/{id}/face/compositionPatch composition parameters (blend mode, z-order, opacity) for a face render group.
🔗Simulators
Virtual display simulators let you build and test face effects with no physical display attached.
/api/v1/simulators/displaysList simulated displays.
/api/v1/simulators/displaysCreate a simulated display.
/api/v1/simulators/displays/{id}Get one simulated display.
/api/v1/simulators/displays/{id}Update a simulated display’s configuration.
/api/v1/simulators/displays/{id}Delete a simulated display.
/api/v1/simulators/displays/{id}/frameThe latest composited frame from a simulated display.
🔗Attachments
Attachment templates describe physical accessories (keycaps, case panels, stands) that clip onto device slots and carry their own LED zones.
/api/v1/attachments/templatesList attachment templates (built-in and user-defined).
/api/v1/attachments/templatesCreate a user-defined attachment template.
/api/v1/attachments/templates/{id}Get one attachment template.
/api/v1/attachments/templates/{id}Update a user-defined attachment template.
/api/v1/attachments/templates/{id}Delete a user-defined attachment template.
/api/v1/attachments/categoriesList attachment categories (keycap-set, case-panel, stand, etc.).
/api/v1/attachments/vendorsList attachment vendors that have templates available.
🔗Control surfaces
Control surfaces expose typed fields and actions for dynamic device or driver configuration (WLED protocol selection, Hue bridge IP, and the like). The web UI reads these to render device-specific settings panels.
/api/v1/control-surfacesList every registered control surface across devices and drivers.
/api/v1/control-surfaces/{surface_id}Get one control surface with its current field values.
/api/v1/control-surfaces/{surface_id}/valuesApply typed field values to a control surface.
Request body:
{
"fields": {
"protocol": { "type": "enum", "value": "ddp" },
"ip_address": { "type": "ip", "value": "10.0.0.50" }
}
}/api/v1/control-surfaces/{surface_id}/actions/{action_id}Invoke a typed control-surface action (Discover, Sync, Reset, and so on).
🔗Scenes
Scenes are whole-rig configurations: the effects, zones, and assignments that define how your entire setup lights up. Switching scenes swaps the whole rig.

/api/v1/scenesList defined scenes.
/api/v1/scenesCreate a named scene. New scenes are born with a default Primary zone, live mutation mode, and the engine’s default scene transition.
Request body:
{
"name": "Late Night",
"description": "Dim amber for late sessions",
"enabled": true,
"mutation_mode": "live"
}/api/v1/scenes/activeThe currently active scene.
/api/v1/scenes/{id}One scene’s configuration.
/api/v1/scenes/{id}Update a scene.
/api/v1/scenes/{id}Delete a scene.
/api/v1/scenes/{id}/activateActivate a scene, applying its effects and controls with the configured transition.
/api/v1/scenes/deactivateDeactivate the current scene, returning to the default free-running state.
🔗Scene zones
Zones are flexible partitions of the scene’s canvas. Each zone owns a set of device outputs and renders its own effect. Zones live under a scene; there is no top-level /zones collection.

/api/v1/scenes/{id}/zonesList a scene’s zones. The response includes groups_revision and an ETag header carrying the same revision for optimistic concurrency.
/api/v1/scenes/{id}/zonesCreate a zone in a scene. Send If-Match with the last seen groups_revision; a stale revision returns 412 Precondition Failed.
Request body:
{
"name": "Desk",
"color": "#80ffea"
}/api/v1/scenes/{id}/zones/{zone_id}Get one scene zone.
/api/v1/scenes/{id}/zones/{zone_id}Update zone metadata. Set make_primary to make a zone the default output zone; that structural edit should carry If-Match.
Request body:
{
"name": "Desk halo",
"description": "Ambient strips behind the monitor",
"brightness": 0.8,
"enabled": true
}/api/v1/scenes/{id}/zones/{zone_id}Delete a zone. The default and display zones cannot be deleted through this route.
/api/v1/scenes/{id}/zones/{zone_id}/devicesAssign device outputs to a zone. Each item may reference an existing device-output ID or carry a full payload.
Request body:
{
"device_zones": [
{ "id": "keyboard-left" }
]
}/api/v1/scenes/{id}/zones/{zone_id}/devices/{device_zone_id}Unassign one device output from a zone.
/api/v1/scenes/{id}/zones/{zone_id}/layoutUpdate one zone’s spatial layout. The route takes a SpatialLayout payload and preserves the zone’s existing output roster, so it is for placement edits only; add or remove outputs through the device routes above. Structural edits should carry If-Match.
Request body:
{
"id": "default-zone-layout",
"name": "Default zone",
"canvas_width": 640,
"canvas_height": 480,
"zones": [],
"default_sampling_mode": { "type": "bilinear" },
"default_edge_behavior": "clamp",
"spaces": null,
"version": 1
}/api/v1/scenes/{id}/unassigned-behaviorSet how outputs not claimed by any zone should render.
Request body:
{
"unassigned_behavior": "off"
}Values are "off", "hold", or { "fallback": "<zone_uuid>" }.
🔗Scene layers
Each zone (render group) stacks layers: effects, faces, and media composited with a blend mode and opacity.
/api/v1/scenes/{id}/groups/{group_id}/layersList the layers in a zone.
/api/v1/scenes/{id}/groups/{group_id}/layersAdd a layer to a zone.
/api/v1/scenes/{id}/groups/{group_id}/layers/orderReorder the layers in a zone.
/api/v1/scenes/{id}/groups/{group_id}/layers/{layer_id}Update one layer (blend mode, opacity, transform, color, source binding).
/api/v1/scenes/{id}/groups/{group_id}/layers/{layer_id}Delete a layer.
/api/v1/scenes/{id}/groups/{group_id}/layers/{layer_id}/controlsPatch the control values on one layer’s source effect.
/api/v1/scenes/{id}/layers/broadcast-mediaBroadcast a media layer across the scene’s zones in one call.
🔗Profiles
Profiles save a full state snapshot (effect, controls, brightness, assignments) that you can restore later.
/api/v1/profilesList saved profiles.
/api/v1/profilesCreate a profile from the current state.
Request body:
{
"name": "Gaming"
}/api/v1/profiles/{id}Get one profile’s saved state.
/api/v1/profiles/{id}Update a profile.
/api/v1/profiles/{id}Delete a profile.
/api/v1/profiles/{id}/applyApply a profile, restoring its effect, controls, and assignments.
🔗Layouts
Layouts define how the effect canvas maps onto physical LED positions, in normalized [0.0, 1.0] coordinates so effects stay resolution-independent.
/api/v1/layoutsList spatial layouts.
/api/v1/layoutsCreate a spatial layout.
/api/v1/layouts/activeThe active layout.
/api/v1/layouts/active/previewPreview a layout without applying it. Returns the zone-to-LED mapping that would result, so a UI can render it visually.
/api/v1/layouts/{id}One layout’s configuration: device zones, positions, LED mappings.
/api/v1/layouts/{id}Update a layout.
/api/v1/layouts/{id}Delete a layout.
/api/v1/layouts/{id}/applyApply a layout as the active spatial mapping.
🔗Library
The library holds favorites, presets, and playlists.
🔗Favorites
/api/v1/library/favoritesList favorited effects.
/api/v1/library/favoritesAdd an effect to favorites.
Request body:
{
"effect_id": "borealis"
}/api/v1/library/favorites/{effect}Remove an effect from favorites. The path key is the effect ID, not a favorite ID.
🔗Presets
/api/v1/library/presetsList saved presets (effect plus control-value combinations).
/api/v1/library/presetsSave the current effect and controls as a named preset.
/api/v1/library/presets/{id}Get one preset.
/api/v1/library/presets/{id}Update a preset.
/api/v1/library/presets/{id}Delete a preset.
/api/v1/library/presets/{id}/applyApply a preset.
🔗Playlists
/api/v1/library/playlistsList playlists.
/api/v1/library/playlistsCreate a playlist of effects with transition timing.
/api/v1/library/playlists/activeThe currently running playlist, if any.
/api/v1/library/playlists/{id}Get one playlist.
/api/v1/library/playlists/{id}Update a playlist.
/api/v1/library/playlists/{id}Delete a playlist.
/api/v1/library/playlists/{id}/activateStart a playlist. Effects cycle on the playlist’s timing.
/api/v1/library/playlists/stopStop the running playlist.
🔗Settings and audio
/api/v1/settings/brightnessThe current global brightness level.
/api/v1/settings/brightnessSet global brightness, 0.0 to 1.0.
Request body:
{
"brightness": 0.8
}/api/v1/audio/devicesList available audio capture devices for reactive effects. Pick the monitor of your output, not a microphone, if you want lights to follow what’s playing.
🔗Screen capture
/api/v1/capture/source/pickOpen the platform picker so the user can choose a screen or window capture source for screen-reactive effects.
🔗Configuration
/api/v1/configShow the full current configuration.
/api/v1/config/get?key=path.to.keyGet one configuration value by dotted key path.
/api/v1/config/setSet a configuration value.
Request body:
{
"key": "audio.enabled",
"value": true
}/api/v1/config/resetReset a configuration value to its default.
Request body:
{
"key": "audio.device_name"
}🔗Diagnostics
/api/v1/diagnoseRun system diagnostics: device connectivity, audio capture, effect-engine health, and configuration validity. This is the same check the diagnose CLI command and MCP tool run.
/api/v1/diagnose/memoryA memory diagnostics snapshot: daemon RSS (which includes the in-process Servo renderer), canvas buffer size, and allocation counters. Useful when chasing slow memory growth.
🔗Assets
User media (images, video) used by media layers.
/api/v1/assetsList media assets.
/api/v1/assetsUpload a media asset.
/api/v1/assets/{id}Get asset metadata.
/api/v1/assets/{id}Update asset metadata.
/api/v1/assets/{id}Delete an asset.
/api/v1/assets/{id}/blobFetch the raw asset bytes.
/api/v1/assets/{id}/thumbnailFetch the asset thumbnail.
🔗Where to go next
For the streaming side of the daemon (live frames, spectrum, preview canvases, and REST-over-WebSocket), see the WebSocket protocol. To drive the same surface from a shell or an agent, see the CLI reference and the Agents and MCP guide. The request and response body shapes for the devices, effects, scenes, and zones domains are defined once in hypercolor-types::api and shared by the daemon and both UIs.