API Reference¶
r0astr exposes a REST API and a WebSocket server for real-time control and integration. Both run on the same port as the dev server.
-
REST API
CRUD operations on panels, playback control, health check.
Base URL
http://localhost:5173 -
WebSocket
Real-time state sync, remote control, slider updates, metronome.
URL
ws://localhost:5173/ws
Authentication & CORS¶
r0astr supports optional API key authentication and configurable CORS origins via server.config.json.
Server Configuration File¶
Create or edit server.config.json in the project root:
Copy server.config.example.json as a starting point.
| Field | Default | Description |
|---|---|---|
cors.allowedOrigins |
["*"] |
Array of allowed origins. Use ["*"] to allow all, or list specific origins like ["http://192.168.1.50:5173"] |
auth.apiKey |
"" |
API key string. When empty, no authentication is required |
When to set an API key
If you're running r0astr on an untrusted or shared network, set an API key to prevent unauthorized access. On a private home network, leaving it empty is fine.
API Key Authentication¶
When an API key is configured:
- Localhost requests are always allowed without a key
- Remote REST requests must include the
X-API-Keyheader - Remote WebSocket connections must include the key as a query parameter
Public endpoint — /api/server-config/auth-required requires no authentication and returns whether a key is needed:
Clients can call this first to decide whether to prompt for a key.
CORS¶
CORS headers apply to /api/* and /health endpoints. When allowedOrigins is set to ["*"], all origins are accepted. To restrict access:
Requests from unlisted origins will not receive CORS headers and will be blocked by the browser.
Server Config API (localhost only)¶
These endpoints are only accessible from localhost and are used by the settings UI.
GET /api/server-config¶
Returns the current server configuration. The API key is masked.
POST /api/server-config¶
Update server configuration. Use "__KEEP__" as the apiKey value to preserve the existing key without resending it.
curl -X POST http://localhost:5173/api/server-config \
-H "Content-Type: application/json" \
-d '{"cors": {"allowedOrigins": ["*"]}, "auth": {"apiKey": "__KEEP__"}}'
REST API¶
All endpoints return JSON.
Headers: Content-Type: application/json — plus X-API-Key: <key> if authentication is enabled (see above)
GET /health¶
Health check endpoint.
GET /api/panels¶
Returns all panels and their current state.
| Field | Type | Description |
|---|---|---|
id |
string | Unique panel identifier |
title |
string | Display name |
code |
string | Current pattern code |
playing |
boolean | Playback state |
position |
object | { x, y } screen coordinates |
size |
object | { w, h } dimensions in pixels |
zIndex |
number | Stacking order |
stale |
boolean | Code changed since last play |
POST /api/panels¶
Create a new panel. All fields are optional.
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | no | Display name (auto-generated if omitted) |
code |
string | no | Initial pattern code |
position |
object | no | { x, y } screen coordinates |
size |
object | no | { w, h } dimensions in pixels |
Side effect
Broadcasts panel_created to all WebSocket clients.
DELETE /api/panels/:id¶
Delete a panel by ID.
Side effect
Broadcasts panel_deleted to all WebSocket clients.
POST /api/panels/:id/code¶
Update a panel's pattern code.
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | New pattern code |
autoPlay |
boolean | no | If true, evaluate and play immediately |
Side effect
Broadcasts panel_updated to all WebSocket clients. If the panel was playing and autoPlay is false, the panel is marked stale.
POST /api/panels/:id/playback¶
Control a panel's playback state.
| Field | Type | Required | Values |
|---|---|---|---|
state |
string | yes | "play" · "pause" · "toggle" |
Side effect
Broadcasts playback_changed to all WebSocket clients.
WebSocket Protocol¶
All messages are JSON objects with a type field. The server runs alongside Vite on the same port.
Connection¶
If authentication is enabled, include the API key as a query parameter:
Connections from localhost do not require a key. Connections from remote addresses without a valid key receive a 401 Unauthorized and are dropped.
On connect, the server sends:
Registration¶
Register as either the main interface or a remote control:
| Value | Behaviour |
|---|---|
"main" |
Source of truth — receives commands from remotes |
"remote" |
Control surface — receives state broadcasts, automatically gets full_state on connect |
Commands¶
Sent by remote clients, forwarded by the server to the main interface.
Panel Control¶
| Type | Payload | Description |
|---|---|---|
panel.play |
{ panel: 1 } |
Start playback |
panel.pause |
{ panel: 1 } |
Pause playback |
panel.toggle |
{ panel: 1 } |
Toggle play/pause |
panel.updateCode |
{ panelId, code } |
Update pattern code |
Global Control¶
| Type | Payload | Description |
|---|---|---|
global.stopAll |
— | Stop all panels |
global.updateAll |
— | Re-evaluate all panels |
Slider Control¶
| Type | Payload | Description |
|---|---|---|
master.sliderChange |
{ sliderId, value } |
Change a master panel slider |
panel.sliderChange |
{ panelId, sliderId, value } |
Change a panel slider |
Events¶
Broadcast from the main interface to remote clients.
State Sync¶
| Type | Payload | Description |
|---|---|---|
full_state |
{ panels, masterCode, masterSliders } |
Complete state snapshot (sent on remote connect) |
client.syncPanels |
{ panels: [...] } |
Bulk panel sync |
full_state payload
{
"type": "full_state",
"panels": [
{
"id": "panel-123",
"title": "Instrument 1",
"code": "s(\"bd*4\")",
"playing": true,
"position": { "x": 0, "y": 0 },
"size": { "w": 600, "h": 200 },
"zIndex": 10
}
],
"masterCode": "let SLIDER_LPF = slider(800, 100, 5000);",
"masterSliders": [
{ "id": 0, "label": "LPF", "min": 100, "max": 5000, "default": 800 }
]
}
Panel Lifecycle¶
| Type | Payload | Description |
|---|---|---|
panel_created |
{ panelId, title, code, position, size } |
New panel added |
panel_deleted |
{ panelId } |
Panel removed |
panel_renamed |
{ panelId, title } |
Panel title changed |
panel_updated |
{ panelId, code, autoPlay } |
Pattern code changed |
playback_changed |
{ panelId, playing } |
Play/pause state changed |
Slider State¶
| Type | Payload | Description |
|---|---|---|
master.sliders |
{ sliders: [{ id, label, min, max, default }] } |
Master slider definitions |
master.sliderValue |
{ sliderId, value } |
Master slider value update |
panel.sliders |
{ panelId, sliders: [...] } |
Panel slider definitions |
panel.sliderValue |
{ panelId, sliderId, value } |
Panel slider value update |
Metronome¶
| Type | Payload | Description |
|---|---|---|
metronome.step |
{ step, isDownbeat } |
16-step metronome tick (step: 0–15, isDownbeat: true on step 0) |
Error¶
Quick Examples¶
curl — list panels, start one¶
# List all panels (no auth)
curl http://localhost:5173/api/panels | jq
# List all panels (with auth, from remote device)
curl -H "X-API-Key: your-secret-key" \
http://192.168.1.100:5173/api/panels | jq
# Start panel 1
curl -X POST http://192.168.1.100:5173/api/panels/panel-1/playback \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"state": "play"}'
# Check if auth is required (always public)
curl http://192.168.1.100:5173/api/server-config/auth-required
JavaScript — WebSocket remote¶
// Include apiKey param if auth is enabled
const ws = new WebSocket("ws://192.168.1.100:5173/ws?apiKey=your-secret-key");
ws.onopen = () => {
ws.send(JSON.stringify({
type: "client.register",
clientType: "remote"
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "full_state") {
console.log("Panels:", msg.panels);
}
};
// Toggle panel 1
ws.send(JSON.stringify({
type: "panel.toggle",
panel: 1
}));
See Remote Control for the built-in remote interface, or Plugin Development for building integrations.