-
-
Notifications
You must be signed in to change notification settings - Fork 2
en Api Reference
Base URL: http://localhost/api/v1
Most endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <module_token>
Tokens are stored on disk in /secure/module_tokens/. For development, set the DEV_MODULE_TOKEN environment variable.
All authenticated endpoints are rate-limited to 120 requests per 60 seconds per client. This is configurable via RateLimitMiddleware. Exceeding the limit returns 429 Too Many Requests.
| Header | Description |
|---|---|
Authorization |
Bearer <token> (required for most endpoints) |
X-Request-Id |
Auto-generated UUID per request (injected by RequestIdMiddleware) |
Interactive API docs are available at /docs when the DEBUG=true environment variable is set. Disabled in production.
Returns the current health status of the SelenaCore instance. No authentication required.
Response 200:
{
"status": "ok",
"version": "0.3.142-beta+0644435",
"mode": "normal",
"uptime": 3600,
"integrity": "ok"
}| Field | Type | Values |
|---|---|---|
status |
string | "ok" |
mode |
string |
"normal" or "safe_mode"
|
uptime |
int | Seconds since startup |
integrity |
string |
"ok" or "violation"
|
Returns detailed system and hardware information. Requires authentication.
Response 200:
{
"initialized": true,
"wizard_completed": true,
"version": "0.3.142-beta+0644435",
"hardware": {
"model": "raspberrypi",
"ram_total_mb": 8192,
"has_hdmi": false,
"has_camera": false
},
"audio": {
"inputs": [],
"outputs": []
},
"display_mode": "headless"
}| Field | Type | Description |
|---|---|---|
initialized |
bool | Whether the core has completed first-run initialization |
wizard_completed |
bool | Whether the onboarding wizard has been completed |
display_mode |
string |
"headless" or display identifier |
All device endpoints require authentication.
List all registered devices.
Response 200:
{
"devices": [
{
"device_id": "uuid-string",
"name": "Kitchen Light",
"type": "actuator",
"protocol": "zigbee",
"state": {"power": true, "brightness": 80},
"capabilities": ["turn_on", "turn_off", "set_brightness"],
"last_seen": 1711900000.0,
"module_id": "protocol-bridge",
"meta": {"manufacturer": "IKEA"}
}
]
}Register a new device.
Request:
{
"name": "Kitchen Light",
"type": "actuator",
"protocol": "zigbee",
"capabilities": ["turn_on", "turn_off"],
"meta": {}
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Human-readable device name |
type |
string | yes | One of: sensor, actuator, controller, virtual
|
protocol |
string | yes | Communication protocol (e.g. zigbee, mqtt, http) |
capabilities |
list[string] | yes | Supported actions |
meta |
object | no | Arbitrary metadata |
Response 201: DeviceResponse (same schema as items in GET /devices).
Publishes a device.registered event on the event bus.
Retrieve a single device by its UUID.
Response 200: DeviceResponse
Response 404:
{"detail": "Device not found"}Update the state of a device.
Request:
{
"state": {"power": true, "brightness": 80}
}The state object is a free-form dictionary. Its keys depend on the device capabilities.
Response 200: DeviceResponse (with updated state)
Publishes a device.state_changed event containing both old_state and new_state.
Remove a device from the registry.
Response 204: No body.
Publishes a device.removed event.
Search devices by entity_type, location, and/or keyword.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
entity_type |
string |
Filter by entity type (e.g. "light", "thermostat") |
location |
string |
Filter by location (e.g. "kitchen", "bedroom") |
keyword |
string |
Search by keyword |
Response 200: DeviceListResponse
All radio endpoints require authentication. Radio stations are used by the media-player module and LLM prompt builder. Names are stored in dual-language format: name_user (original) and name_en (auto-translated to English).
List all radio stations.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
enabled_only |
bool |
Return only enabled stations (default: false) |
genre |
string |
Filter by genre (partial match, checks both genre_en and genre_user) |
Response 200:
{
"stations": [
{
"id": 1,
"name_user": "Jazz FM",
"name_en": "Jazz FM",
"stream_url": "https://example.com/jazz.mp3",
"genre_user": "jazz",
"genre_en": "jazz",
"country": "UK",
"logo_url": "",
"enabled": true,
"favourite": false,
"patterns_en": ["play jazz fm", "jazz radio"]
}
]
}Create a new radio station. Names are auto-translated to English via LLM.
Request:
{
"name_user": "Jazz FM",
"stream_url": "https://example.com/jazz.mp3",
"genre_user": "jazz",
"country": "UK",
"logo_url": "",
"enabled": true,
"favourite": false
}Response 201: RadioStationResponse
Update a radio station. Only provided fields are updated. Name/genre are re-translated if changed.
Request: Partial RadioStationCreate (all fields optional).
Response 200: RadioStationResponse
Remove a radio station.
Response 204: No body.
All scene endpoints require authentication. Scenes are named sets of device actions. Names are stored in dual-language format: name_user (original) and name_en (auto-translated).
List all scenes.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
enabled_only |
bool |
Return only enabled scenes (default: false) |
Response 200:
{
"scenes": [
{
"id": 1,
"name_user": "Movie Night",
"name_en": "Movie Night",
"actions": [
{"device_id": "uuid-...", "state": {"on": false}},
{"device_id": "uuid-...", "state": {"brightness": 20}}
],
"trigger": "",
"enabled": true,
"patterns_en": ["movie night", "start movie scene"]
}
]
}Create a new scene. Name is auto-translated to English via LLM.
Request:
{
"name_user": "Movie Night",
"actions": [
{"device_id": "uuid-...", "state": {"on": false}}
],
"trigger": "",
"enabled": true
}Response 201: SceneResponse
Update a scene. Only provided fields are updated. Name is re-translated if changed.
Request: Partial SceneCreate (all fields optional).
Response 200: SceneResponse
Remove a scene.
Response 204: No body.
All event endpoints require authentication.
Publish a custom event to the event bus.
Request:
{
"type": "my.custom_event",
"source": "my-module",
"payload": {"key": "value"}
}| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | Event type identifier (dot-separated namespace) |
source |
string | yes | Module or component that generated the event |
payload |
object | no | Arbitrary event data |
Response 201:
{
"event_id": "uuid",
"type": "my.custom_event",
"timestamp": 1711900000.0
}Response 403: Returned when a module attempts to publish a core.* event. Only the core system may emit events in the core namespace.
Subscribe to events via webhook callback.
Deprecated. Use the Module Bus WebSocket instead.
Request:
{
"event_types": ["device.state_changed"],
"webhook_url": "http://localhost:8100/webhook"
}Response 201:
{
"subscription_id": "uuid",
"event_types": ["device.state_changed"],
"webhook_url": "http://localhost:8100/webhook"
}All module endpoints require authentication.
List all installed modules.
Response 200:
{
"modules": [
{
"name": "weather-module",
"version": "1.0.0",
"type": "UI",
"status": "RUNNING",
"runtime_mode": "always_on",
"port": 0,
"installed_at": 1711900000.0,
"ui": {
"icon": "icon.svg",
"widget": {"file": "widget.html", "size": "2x2"}
}
}
]
}| Field | Type | Description |
|---|---|---|
type |
string | Module type (e.g. UI, SYSTEM, SERVICE) |
status |
string |
VALIDATING, READY, RUNNING, STOPPED, ERROR
|
runtime_mode |
string |
always_on or on_demand
|
port |
int | Assigned port (0 if not applicable) |
ui |
object or null | UI widget configuration, if the module provides one |
Install a module from a ZIP archive. Uses multipart form upload.
Request:
Content-Type: multipart/form-data
Field: module (file, .zip)
Response 201:
{
"name": "my-module",
"status": "VALIDATING",
"message": "Module uploaded, validation in progress"
}Installation is asynchronous. Use the SSE stream endpoint to track progress.
Server-Sent Events stream for tracking module installation and lifecycle changes.
Response: text/event-stream
data: {"status": "VALIDATING", "message": "Manifest validated, installing..."}
data: {"status": "READY", "message": "Validation passed, starting..."}
data: {"status": "RUNNING", "message": "Module started"}
A heartbeat message is sent every 30 seconds if there are no status updates.
Start a stopped module.
Response 200:
{"name": "my-module", "status": "RUNNING"}Stop a running module.
Response 200:
{"name": "my-module", "status": "STOPPED"}Response 403: Returned when attempting to stop a SYSTEM module. System modules cannot be stopped.
Remove an installed module and clean up its resources.
Response 204: No body.
Response 403: Returned when attempting to remove a SYSTEM module. System modules cannot be removed.
All secret endpoints require authentication.
List stored secret identifiers. Values are never returned in plaintext.
Store an OAuth token or other secret. Secrets are encrypted at rest using AES-256-GCM.
All integrity endpoints require authentication.
Returns the current status of the Integrity Agent, which monitors file and configuration tampering.
Deprecated. Intents are now managed via the Module Bus
announcemechanism. These REST endpoints remain for backward compatibility but will be removed in a future release.
List intents announced via Module Bus.
Register new intents. Use Module Bus announce instead.
WebSocket endpoint for real-time Module Bus communication. Modules connect here to announce capabilities, subscribe to events, and exchange messages with the core.
Pass the module token as the token query parameter.
See Module Bus Protocol for the full message format and handshake reference.
Base: /api/ui
These routes are intended for the local web UI only. They are protected by iptables rules (localhost access only) and do not require Bearer tokens.
| Route | Description |
|---|---|
POST /api/ui/setup/* |
Onboarding wizard steps |
GET /api/ui/setup/vosk/catalog |
Vosk speech-to-text model catalog |
| Voice engine endpoints | Manage STT/TTS engine configuration |
| Module UI routing | Serve module widget files and icons |
| Method | Route | Description |
|---|---|---|
| GET | /api/ui/setup/audio/devices |
List detected ALSA input/output devices |
| POST | /api/ui/setup/audio/select |
Save {input, output} device selection to core.yaml |
| POST | /api/ui/setup/audio/test/output |
Play speaker-test (L/R voice) at configured volume |
| POST | /api/ui/setup/audio/test/input |
Record 3s from mic {device}, measure peak, play back on {output_device}
|
| GET | /api/ui/setup/audio/mic-level |
Quick 1s mic sample → {level: 0.0-1.0}
|
| GET | /api/ui/setup/audio/levels |
Get {output_volume, input_gain} from config |
| POST | /api/ui/setup/audio/levels |
Set {output_volume?, input_gain?} — persists + applies via amixer |
| GET | /api/ui/setup/audio/sources |
List audio source modules → {sources: [{module, name, volume}]}
|
| POST | /api/ui/setup/audio/sources/volume |
Set {module, volume} for a specific audio source |
Admin-UI wrapper around bluetoothctl (bluez). bluetoothctl runs
inside the selena-core container; the host DBus socket and
/var/lib/bluetooth are bind-mounted so paired state persists across
restarts. Implementation: core/bluetooth.py.
| Method | Route | Description |
|---|---|---|
| GET | /api/ui/setup/bluetooth/status |
Adapter state → {available, powered, discovering, pairable, address, name, alias}. available=false when no controller or bluetoothctl binary. |
| POST | /api/ui/setup/bluetooth/power |
Body {enable: bool} — bluetoothctl power on/off. 503 if no adapter. |
| GET | /api/ui/setup/bluetooth/devices |
List paired devices → {devices: [{mac, name, alias, icon, connected, trusted, paired}]}. |
| GET | /api/ui/setup/bluetooth/scan?timeout=N |
Run discovery for N seconds (clamped 3–30, default 10). Returns only devices with resolved names — MAC-only broadcasts (iPhones with random MAC) are hidden. |
| POST | /api/ui/setup/bluetooth/connect/{mac} |
Connect to an already-paired device. 400 connect_failed on refusal. |
| POST | /api/ui/setup/bluetooth/disconnect/{mac} |
Disconnect without unpairing. |
| POST | /api/ui/setup/bluetooth/unpair/{mac} |
bluetoothctl remove <mac> — removes pairing. |
| POST | /api/ui/setup/bluetooth/rename |
Body {mac, alias} — sets persistent alias (Device1.Alias). Uses bluetoothctl, falls back to busctl set-property on older bluez. |
| GET | /api/ui/setup/bluetooth/pair?mac=… |
SSE stream of pair-session events (see below). |
| POST | /api/ui/setup/bluetooth/pair/respond |
Body {mac, pin?, confirm?} — feeds PIN or yes/no into the active session. 404 no_active_session if nothing in progress for that MAC. |
| POST | /api/ui/setup/bluetooth/pair/cancel?mac=… |
Abort an active pair session (kills the bluetoothctl stdin pipe). |
Pair SSE events (one JSON object per data: line):
data: {"type": "started"}
data: {"type": "pin_required", "prompt": "[agent] Enter PIN code:"}
data: {"type": "confirm_code", "code": "123456"}
data: {"type": "success"}
data: {"type": "failed", "reason": "authentication_failed"}
Session lifetime is capped at 90s; the stream closes after success
or failed. "Just Works" devices (most BT headphones/speakers) go
from started straight to success — pin_required /
confirm_code only appear for keyboards, phones, cars, etc.
All errors return a JSON body with a detail field.
Standard error:
{
"detail": "Error message"
}Validation error (422 Unprocessable Entity):
{
"detail": {"errors": ["error1", "error2"]}
}| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No Content (successful deletion) |
| 400 | Bad Request |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Too Many Requests (rate limit exceeded) |
| 500 | Internal Server Error |
Events use a dot-separated namespace. The following namespaces are defined:
Reserved for the core system. Modules cannot publish these.
| Event | Description |
|---|---|
core.startup |
Core has started |
core.shutdown |
Core is shutting down |
core.integrity_violation |
File or config tampering detected |
core.integrity_restored |
Integrity check passed after a previous violation |
core.safe_mode_entered |
System entered safe mode |
core.safe_mode_exited |
System exited safe mode |
| Event | Description |
|---|---|
device.state_changed |
Device state was updated (includes old_state and new_state) |
device.registered |
New device was added |
device.removed |
Device was deleted |
device.offline |
Device stopped responding |
device.online |
Device reconnected |
device.discovered |
New device discovered on the network |
| Event | Description |
|---|---|
module.installed |
Module was installed |
module.started |
Module was started |
module.stopped |
Module was stopped |
module.error |
Module encountered an error |
module.removed |
Module was uninstalled |
| Event | Description |
|---|---|
sync.command_received |
Remote command received from cloud sync |
sync.command_ack |
Command acknowledgment sent |
sync.connection_lost |
Cloud sync connection lost |
sync.connection_restored |
Cloud sync connection restored |
| Event | Description |
|---|---|
voice.wake_word |
Wake word detected |
voice.recognized |
Speech recognized |
voice.intent |
Intent extracted from speech |
voice.response |
Voice response generated |
voice.privacy_on |
Microphone muted / privacy mode enabled |
voice.privacy_off |
Microphone unmuted / privacy mode disabled |
🤖 This wiki is auto-synced from docs/ in the main repo. Hand-edits on the wiki UI get overwritten on the next push. Open a PR against the main repo instead.
MIT License · Sponsor · Ko-fi
SelenaCore
🇬🇧 English
Getting started
Architecture
Voice & translation
Hardware integration
Development
- Modules overview
- Module development
- System module development
- Module API guide
- Module bus protocol
- Widget development
- User manager / auth
Reference
🇺🇦 Українська
Початок
Архітектура
Голос і переклад
Інтеграція заліза
Розробка
- Розробка модулів
- Розробка системних модулів
- Module API
- Module bus
- Widget development
- User manager / auth
Довідник