Skip to content

API Reference

Sowel exposes a REST API under /api/v1/ and a WebSocket endpoint at /ws. All endpoints require authentication unless noted otherwise. Responses use JSON.

Base URL: http://<host>:3000

Authentication: Pass a JWT access token as Authorization: Bearer <token> header, or an API token as Authorization: Bearer swl_<token>.


Table of Contents


Authentication

Public endpoints -- no auth required for status and setup.

Method Path Description
GET /api/v1/auth/status Check if first-run setup is required. Returns { setupRequired: boolean }.
POST /api/v1/auth/setup Create the first admin user (first-run only). Body: { username, password, displayName, language? }. Returns JWT tokens.
POST /api/v1/auth/login Authenticate. Body: { username, password }. Returns { accessToken, refreshToken }. Rate limited: 10 req/min.
POST /api/v1/auth/refresh Refresh access token. Body: { refreshToken }. Returns new token pair.
POST /api/v1/auth/logout Invalidate refresh token. Body: { refreshToken }. Returns 204.

Current User (Me)

Authenticated user's own profile and tokens.

Method Path Description
GET /api/v1/me Get current user profile.
PUT /api/v1/me Update display name. Body: { displayName }.
PUT /api/v1/me/preferences Update preferences (language, theme, etc.). Body: { preferences }.
PUT /api/v1/me/password Change password. Body: { currentPassword, newPassword }.
GET /api/v1/me/tokens List own API tokens.
POST /api/v1/me/tokens Create API token. Body: { name, expiresAt? }. Returns token string (shown only once).
DELETE /api/v1/me/tokens/:id Revoke an API token. Returns 204.

Users (Admin)

All user management routes require admin role.

Method Path Description
GET /api/v1/users List all users.
POST /api/v1/users Create user. Body: { username, password, displayName, role }.
PUT /api/v1/users/:id Update user. Body: { displayName?, role?, enabled? }.
DELETE /api/v1/users/:id Delete user. Cannot delete self or last admin. Returns 204.

Devices

Method Path Description
GET /api/v1/devices List all devices with current data and orders.
GET /api/v1/devices/:id Get device with data and orders.
PUT /api/v1/devices/:id Update device. Body: { name?, zoneId? }.
DELETE /api/v1/devices/:id Remove device. Returns 204.
GET /api/v1/devices/suggest Suggest compatible devices for an equipment type. Query: ?type=<equipmentType>.
GET /api/v1/devices/:id/raw Get raw integration expose data for a device.

Equipments

Method Path Description
GET /api/v1/equipments List all equipments with bindings and current data.
GET /api/v1/equipments/:id Get equipment with bindings and current data.
POST /api/v1/equipments Create equipment. Body: { name, type, zoneId, icon?, description?, deviceIds? }. If deviceIds provided, auto-bindings are created.
PUT /api/v1/equipments/:id Update equipment. Body: { name?, type?, zoneId?, icon?, description?, enabled? }.
DELETE /api/v1/equipments/:id Delete equipment. Returns 204.
POST /api/v1/equipments/:id/orders/:alias Execute an equipment order. Body: { value }.

Data Bindings

Method Path Description
POST /api/v1/equipments/:id/data-bindings Add a DataBinding. Body: { deviceDataId, alias }.
DELETE /api/v1/equipments/:id/data-bindings/:bindingId Remove a DataBinding. Returns 204.

Order Bindings

Method Path Description
POST /api/v1/equipments/:id/order-bindings Add an OrderBinding. Body: { deviceOrderId, alias }.
DELETE /api/v1/equipments/:id/order-bindings/:bindingId Remove an OrderBinding. Returns 204.

Zones

Method Path Description
GET /api/v1/zones List all zones as a tree structure.
GET /api/v1/zones/:id Get zone with children.
POST /api/v1/zones Create zone. Body: { name, parentId?, icon?, description?, displayOrder? }.
PUT /api/v1/zones/:id Update zone. Body: { name?, parentId?, icon?, description?, displayOrder? }.
DELETE /api/v1/zones/:id Delete zone. Returns 204.
PUT /api/v1/zones/reorder Reorder sibling zones. Body: { parentId, orderedIds }. Returns 204.
GET /api/v1/zones/aggregation Get aggregated data for all zones (temperature, motion, lightsOn, etc.).
POST /api/v1/zones/:id/orders/:orderKey Execute zone-level order (e.g. allLightsOff, allShuttersClose). Body: { value? }.

Modes

Method Path Description
GET /api/v1/modes List all modes with details.
GET /api/v1/modes/:id Get mode with impacts and state.
POST /api/v1/modes Create mode. Body: { name, icon?, description? }.
PUT /api/v1/modes/:id Update mode. Body: { name?, icon?, description? }.
DELETE /api/v1/modes/:id Delete mode. Returns 204.
POST /api/v1/modes/:id/activate Activate mode.
POST /api/v1/modes/:id/deactivate Deactivate mode.
POST /api/v1/modes/:id/apply-to-zone/:zoneId Apply mode impacts to a specific zone.

Zone Mode Impacts

Method Path Description
GET /api/v1/zones/:zoneId/mode-impacts Get mode impacts for a zone.
PUT /api/v1/modes/:id/impacts/:zoneId Set zone impact actions. Body: { actions: ZoneModeImpactAction[] }.
DELETE /api/v1/modes/:id/impacts/:zoneId Remove zone impact. Returns 204.

Mode Triggers

Method Path Description
GET /api/v1/modes/:id/triggers Get button bindings that trigger this mode.

Calendar

Method Path Description
GET /api/v1/calendar/profiles List all calendar profiles.
GET /api/v1/calendar/active Get the active profile with its slots.
PUT /api/v1/calendar/active Set the active profile. Body: { profileId }.
GET /api/v1/calendar/profiles/:id/slots List slots for a profile.
POST /api/v1/calendar/profiles/:id/slots Add a slot. Body: { days, time, modeActions }.
PUT /api/v1/calendar/slots/:slotId Update a slot. Body: { days?, time?, modeActions? }.
DELETE /api/v1/calendar/slots/:slotId Delete a slot. Returns 204.

Recipes

Recipe Definitions

Method Path Description
GET /api/v1/recipes List available recipe definitions (templates).
GET /api/v1/recipes/:recipeId Get recipe definition with slots and i18n.

Recipe Instances

Method Path Description
GET /api/v1/recipe-instances List all active recipe instances.
POST /api/v1/recipe-instances Create instance. Body: { recipeId, params }.
PUT /api/v1/recipe-instances/:id Update instance params. Body: { params }.
DELETE /api/v1/recipe-instances/:id Stop and delete instance. Returns 204.
POST /api/v1/recipe-instances/:id/enable Enable a disabled instance.
POST /api/v1/recipe-instances/:id/disable Disable (pause) a running instance.
POST /api/v1/recipe-instances/:id/actions Send an action to a running recipe. Body: { action, payload? }.
GET /api/v1/recipe-instances/:id/log Get execution log. Query: ?limit=50.

Dashboard

Method Path Description
GET /api/v1/dashboard/widgets List all dashboard widgets ordered by display order.
POST /api/v1/dashboard/widgets Create widget (admin). Body: { type, equipmentId?, zoneId?, family?, label?, icon? }.
PATCH /api/v1/dashboard/widgets/:id Update widget label, icon, or config (admin). Body: { label?, icon?, config? }.
DELETE /api/v1/dashboard/widgets/:id Delete widget (admin). Returns 204.
PUT /api/v1/dashboard/widgets/order Reorder widgets (admin). Body: { order: string[] }.

Charts

Method Path Description
GET /api/v1/charts List saved chart configurations.
GET /api/v1/charts/:id Get a chart configuration.
POST /api/v1/charts Create chart. Body: { name, config }.
PUT /api/v1/charts/:id Update chart. Body: { name?, config? }.
DELETE /api/v1/charts/:id Delete chart. Returns 204.

Energy

Method Path Description
GET /api/v1/energy/status Energy module status (available, sources, tariff configured).
GET /api/v1/energy/history Query energy history. Query: ?period=day&date=2026-01-15. Periods: day, week, month, year. Returns HP/HC breakdown and production data.
GET /api/v1/energy/by-usage Per-submeter consumption time series for the same period buckets as /energy/history. Query: ?period=day&date=2026-01-15. Returns one series per submeter energy_meter plus an other residual (max(0, total - Σ submeters)) and per-equipment totals.
GET /api/v1/settings/energy/tariff Get tariff configuration (HP/HC schedules and prices).
PUT /api/v1/settings/energy/tariff Update tariff configuration. Body: { schedules, prices }.

History

Method Path Description
GET /api/v1/history/status History module status (connected, historized bindings count, stats).
GET /api/v1/history/retention Retention and downsampling status for all InfluxDB buckets/tasks.
GET /api/v1/history/bindings/:equipmentId List historize settings for an equipment's data bindings.
PUT /api/v1/history/bindings/:equipmentId/:bindingId Set historize flag. Body: { historize } (null, 0, or 1).
GET /api/v1/history/sparkline/zone/:zoneId/:category Zone-level 24h sparkline data (e.g. temperature trend).
GET /api/v1/history/sparkline/:equipmentId/:alias Equipment-level 24h sparkline data.
GET /api/v1/history/:equipmentId List historized aliases for an equipment.
GET /api/v1/history/:equipmentId/:alias Query time-series data. Query: ?from=-24h&to=&aggregation=auto. Aggregations: raw, 1h, 1d, auto.

Integrations (Admin)

Admin-only routes for managing device integration plugins.

Method Path Description
GET /api/v1/integrations List all integrations with status, settings, and device counts.
POST /api/v1/integrations/:id/start Start an integration.
POST /api/v1/integrations/:id/stop Stop an integration.
POST /api/v1/integrations/:id/restart Restart an integration (stop + start).
POST /api/v1/integrations/:id/refresh Force a data refresh (polling integrations only).

Plugins (Admin)

Admin-only routes for third-party plugin management.

Method Path Description
GET /api/v1/plugins List installed plugins.
GET /api/v1/plugins/store List available plugins from the registry.
POST /api/v1/plugins/install Install from GitHub. Body: { repo } (e.g. "owner/repo").
POST /api/v1/plugins/:id/uninstall Uninstall a plugin.
POST /api/v1/plugins/:id/enable Enable a plugin (loads and starts it).
POST /api/v1/plugins/:id/disable Disable a plugin (stops and unloads it).

Settings (Admin)

Admin-only key-value settings store (used for integration config, home settings, etc.).

Method Path Description
GET /api/v1/settings Get all settings.
PUT /api/v1/settings Update settings. Body: key-value object { "key": "value", ... }.

MQTT Brokers

External MQTT brokers for outbound publishing.

Method Path Description
GET /api/v1/mqtt-brokers List all MQTT brokers.
POST /api/v1/mqtt-brokers Create broker. Body: { name, url, username?, password? }.
PUT /api/v1/mqtt-brokers/:id Update broker. Body: { name?, url?, username?, password? }.
DELETE /api/v1/mqtt-brokers/:id Delete broker. Returns 204.

MQTT Publishers

Outbound MQTT publishers that push Sowel data to external brokers. Each publisher targets a single MQTT topic and can have multiple data mappings (equipment, zone, or recipe sources). When onChangeOnly is enabled, the publisher only publishes when a value actually changes — useful to avoid flooding external displays with periodic heartbeats.

Each mapping carries its own enabled flag (default true). Disabled mappings are skipped in live publishing, the initial snapshot, and the manual "Test" button — so a seasonal source can be silenced without losing its source/key wiring. The publisher-level enabled flag still wins: if the publisher is off, all its mappings are off regardless of their per-mapping flag.

Method Path Description
GET /api/v1/mqtt-publishers List all publishers with mappings.
GET /api/v1/mqtt-publishers/:id Get publisher with mappings.
POST /api/v1/mqtt-publishers Create publisher. Body: { name, brokerId, topic, enabled?, onChangeOnly? }.
PUT /api/v1/mqtt-publishers/:id Update publisher. Body: { name?, brokerId?, topic?, enabled?, onChangeOnly? }.
DELETE /api/v1/mqtt-publishers/:id Delete publisher. Returns 204.
POST /api/v1/mqtt-publishers/:id/test Test publish a snapshot.
POST /api/v1/mqtt-publishers/:id/mappings Add data mapping. Body: { publishKey, sourceType, sourceId, sourceKey, enabled? }. enabled defaults to true when omitted.
PUT /api/v1/mqtt-publishers/:id/mappings/:mappingId Update mapping. Accepts { publishKey?, sourceType?, sourceId?, sourceKey?, enabled? } — the enabled flag toggles publishing on/off without deleting the mapping.
DELETE /api/v1/mqtt-publishers/:id/mappings/:mappingId Delete mapping. Returns 204.

Notification Publishers

Push notifications (currently Telegram) triggered by data changes.

Method Path Description
GET /api/v1/notification-publishers List all notification publishers with mappings.
GET /api/v1/notification-publishers/:id Get publisher with mappings.
POST /api/v1/notification-publishers Create publisher. Body: { name, channelType, channelConfig, enabled? }.
PUT /api/v1/notification-publishers/:id Update publisher.
DELETE /api/v1/notification-publishers/:id Delete publisher. Returns 204.
POST /api/v1/notification-publishers/:id/test-channel Test the notification channel (sends a test message).
POST /api/v1/notification-publishers/:id/test Test the full publisher (trigger mappings).
POST /api/v1/notification-publishers/:id/mappings Add trigger mapping. Body: { message, sourceType, sourceId, sourceKey, throttleMs? }.
PUT /api/v1/notification-publishers/:id/mappings/:mappingId Update mapping.
DELETE /api/v1/notification-publishers/:id/mappings/:mappingId Delete mapping. Returns 204.

Button Actions

Map physical button presses (Zigbee buttons, etc.) to actions (mode activation, equipment orders, recipe toggles).

Method Path Description
GET /api/v1/equipments/:id/action-bindings List action bindings for a button equipment.
POST /api/v1/equipments/:id/action-bindings Create binding. Body: { actionValue, effectType, config }. Effect types: mode_activate, mode_toggle, equipment_order, recipe_toggle.
PUT /api/v1/equipments/:id/action-bindings/:bindingId Update binding.
DELETE /api/v1/equipments/:id/action-bindings/:bindingId Delete binding. Returns 204.

Activity

Recent engine events for the zone-view activity panel. Reads from the in-memory ring buffer (24h retention, capped at 2000 entries, reset on restart). See Zones — Activity feed for the user-facing description.

Method Path Description
GET /api/v1/activity List activity items. Query: ?zoneId=<uuid>&includeDescendants=true&limit=100. limit is clamped to [1, 200], default 100.

Filtering: items whose zoneId matches the query parameter are returned, plus items whose zoneId is null (global events: mode changes, sunrise/sunset, system alarms). When includeDescendants=true (default), items from child zones are also returned.

Response shape:

{
  "items": [
    {
      "id": "uuid",
      "timestamp": 1715864400000,
      "category": "order",
      "zoneId": "uuid-of-living-room",
      "message": {
        "template": "order.executed",
        "params": { "equipmentName": "Lumière", "alias": "state", "value": "ON" }
      },
      "source": { "kind": "recipe", "instanceId": "...", "recipeName": "Motion Light" }
    }
  ]
}

Item categories: order, motion, recipe, mode, sunlight, alarm.

Source kinds: recipe, mode, manual, button, external. The source field is only present on category=order items.


Logs (Admin)

Admin-only log access from the in-memory ring buffer.

Method Path Description
GET /api/v1/logs Query logs. Query: ?limit=100&level=error&module=mqtt&search=text&since=ISO. Returns entries, capacity, current level, and available modules.
GET /api/v1/logs/level Get current runtime log level.
PUT /api/v1/logs/level Change runtime log level. Body: { level }. Valid: debug, info, warn, error, fatal, silent.

Backup (Admin)

Admin-only full configuration backup and restore.

Method Path Description
GET /api/v1/backup Export full configuration. Returns ZIP (SQLite JSON + InfluxDB CSVs) or JSON if no InfluxDB data.
POST /api/v1/backup Restore configuration from JSON backup. Body: backup payload with { version: 1, tables }.

Health

No authentication required.

Method Path Description
GET /api/v1/health System health check. Returns status, uptime, integration statuses, device counts, and engine version.

WebSocket

Endpoint: ws://<host>:3000/ws?token=<jwt_or_api_token>

Authentication is passed via the token query parameter. Both JWT access tokens and API tokens (swl_ prefix) are accepted.

Connection

On connection, the server sends a welcome message:

{ "type": "connected", "message": "Connected to Sowel engine", "version": "0.1.0" }

Clients are automatically subscribed to the system topic.

Subscribing to Topics

Send a JSON message to subscribe to additional topics:

{ "type": "subscribe", "topics": ["devices", "equipments", "zones", "modes", "recipes"] }

Available topics: devices, equipments, zones, modes, recipes, calendar, mqtt-publishers, system, logs, activity.

The system topic is always included regardless of subscription.

Event Delivery

Events are batched every 200ms and sent as a JSON array. High-frequency data events are deduplicated per batch -- only the latest value per device/equipment/zone key is sent.

[
  { "type": "device.data.updated", "deviceId": "...", "key": "temperature", "value": 22.5 },
  { "type": "equipment.data.changed", "equipmentId": "...", "alias": "state", "value": "ON" },
  { "type": "zone.data.changed", "zoneId": "...", "key": "temperature", "value": 21.8 }
]

Activity Stream

When subscribed to the activity topic, the server pushes new items as they are produced. Each push is a single event (not batched), shape identical to the items returned by GET /api/v1/activity:

{
  "type": "activity.added",
  "item": {
    "id": "uuid",
    "timestamp": 1715864400000,
    "category": "motion",
    "zoneId": "uuid-of-zone",
    "message": { "template": "motion.detected", "params": { "equipmentName": "PIR Living Room" } }
  }
}

Clients must filter the live stream by their current zone scope (the server broadcasts all items to subscribers without per-client zone filtering).

Log Streaming

When subscribed to the logs topic, log entries are streamed individually (not batched):

{
  "type": "log.entry",
  "level": "info",
  "module": "devices",
  "msg": "Device discovered",
  "time": 1700000000
}