|
| 1 | +# Loxone API Documentation |
| 2 | + |
| 3 | +Reverse-engineered API documentation extracted from the Loxone app bundle (`ftp://<loxone-miniserver>:21/web/commonv2.agz`) and the |
| 4 | +[Lox AudioServer](https://github.com/lox-audioserver/lox-audioserver) TypeScript source (`src/`) which is a community project — not an |
| 5 | +official Loxone document. |
| 6 | + |
| 7 | +**⚠️ Not an official Loxone document!** Use at your own risk. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Table of Contents |
| 12 | + |
| 13 | +- [There are four distinct APIs](#there-are-four-distinct-apis) |
| 14 | +- [API 1: Loxone App ↔ Loxone MiniServer](#api-1-loxone-app--loxone-miniserver-miniserver-apiyaml) |
| 15 | + - [Authentication flow](#authentication-flow) |
| 16 | + - [Control command format](#control-command-format) |
| 17 | + - [State update push (WebSocket)](#state-update-push-websocket) |
| 18 | + - [Selected control types and commands](#selected-control-types-and-commands) |
| 19 | +- [APIs 2 & 3: AudioServer APIs](#apis-2--3-loxone-app--loxone-audioserver-and-loxone-miniserver--loxone-audioserver-openapiyaml) |
| 20 | + - [Shared commands (App + MiniServer)](#shared-commands-app--miniserver) |
| 21 | + - [MiniServer-only commands](#miniserver-only-commands) |
| 22 | + - [Response format](#response-format-both-clients) |
| 23 | + - [WebSocket events](#websocket-events-server--all-connected-clients) |
| 24 | +- [API 4: Lox AudioServer Admin UI](#api-4-lox-audioserver-admin-ui--loxone-audioserver-audioserver-admin-apiyaml) |
| 25 | +- [Viewing the docs](#viewing-the-docs) |
| 26 | +- [Files](#files) |
| 27 | +- [Sources](#sources) |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## There are four distinct APIs |
| 32 | + |
| 33 | +``` |
| 34 | +┌─────────────────────────────────────────────────────────────────────────────┐ |
| 35 | +│ │ |
| 36 | +│ Loxone App ──── (1) ────► Loxone MiniServer │ |
| 37 | +│ │ │ │ |
| 38 | +│ │ (2) │ (3) │ |
| 39 | +│ ▼ ▼ │ |
| 40 | +│ Loxone AudioServer ◄──── (3) ──┘ │ |
| 41 | +│ │ │ |
| 42 | +│ │ (4) │ |
| 43 | +│ ▼ │ |
| 44 | +│ Lox AudioServer Admin UI (browser) │ |
| 45 | +│ │ |
| 46 | +└─────────────────────────────────────────────────────────────────────────────┘ |
| 47 | +``` |
| 48 | + |
| 49 | +| # | Link | Protocol | Spec file | |
| 50 | +| --- | ---------------------------------------------- | ------------------------------------------- | ------------------------------------ | |
| 51 | +| 1 | **Loxone App ↔ Loxone MiniServer** | WebSocket `jdev/sps/io/…` | `miniserver-api.yaml` | |
| 52 | +| 2 | **Loxone App ↔ Loxone AudioServer** | HTTP GET + WebSocket `audio/…` | `openapi.yaml` (App subset) | |
| 53 | +| 3 | **Loxone MiniServer ↔ Loxone AudioServer** | HTTP GET + WebSocket `audio/…` + `secure/…` | `openapi.yaml` (full, superset of 2) | |
| 54 | +| 4 | **Lox AudioServer Admin UI ↔ Lox AudioServer** | REST `/admin/api/…` with cookie auth | `audioserver-admin-api.yaml` | |
| 55 | + |
| 56 | +**Key insight for APIs 2 and 3:** The App and the MiniServer use the **same `audio/…` command protocol**, but connect on **different ports** |
| 57 | +and the MiniServer additionally sends a set of config-sync and pairing commands that the App never sends. |
| 58 | + |
| 59 | +--- |
| 60 | + |
| 61 | +## API 1: Loxone App ↔ Loxone MiniServer (`miniserver-api.yaml`) |
| 62 | + |
| 63 | +The Loxone App controls smart home devices by sending commands to the **Loxone MiniServer** over WebSocket or plain HTTP GET. |
| 64 | + |
| 65 | +**WebSocket endpoint:** `ws://{miniserverHost}/ws/rfc6455` **HTTP mirror:** `GET http://{miniserverHost}/{command}` |
| 66 | + |
| 67 | +### Authentication flow |
| 68 | + |
| 69 | +``` |
| 70 | +1. GET /jdev/cfg/api → discover firmware version & key algorithm |
| 71 | +2. GET /jdev/cfg/getPublicKey → fetch RSA-2048 public key |
| 72 | +3. GET /jdev/sys/gettoken/{hash}/{user}/{permission}/{clientId}/{clientName} |
| 73 | + → obtain auth token (hash = HMAC-SHA1 of credentials) |
| 74 | +4. GET /jdev/sys/authwithtoken/{token}/{user} |
| 75 | + → re-authenticate on reconnect |
| 76 | +5. GET /jdev/sys/refreshtoken/{token}/{user} |
| 77 | + → extend token before expiry |
| 78 | +6. GET /jdev/sys/killtoken/{token}/{user} |
| 79 | + → invalidate token on logout |
| 80 | +``` |
| 81 | + |
| 82 | +### Control command format |
| 83 | + |
| 84 | +``` |
| 85 | +jdev/sps/io/{uuid}/{command} |
| 86 | +jdev/sps/io/{uuid}/{command}/{value} |
| 87 | +``` |
| 88 | + |
| 89 | +`{uuid}` is the control's UUID from the structure file (`GET /data/LoxAPP3.json`). |
| 90 | + |
| 91 | +### State update push (WebSocket) |
| 92 | + |
| 93 | +The MiniServer pushes state changes as text frames: |
| 94 | + |
| 95 | +``` |
| 96 | +{stateUuid}:{value} |
| 97 | +``` |
| 98 | + |
| 99 | +State UUIDs are defined in `control.states` inside the structure file. |
| 100 | + |
| 101 | +### Selected control types and commands |
| 102 | + |
| 103 | +| Control type | Commands | |
| 104 | +| ----------------------- | -------------------------------------------------------------------------- | |
| 105 | +| `Jalousie` (blinds) | Up, Down, Stop, FullUp, FullDown, Auto, shade/{pos} | |
| 106 | +| `LightControllerV2` | On, Off, plus, minus, setMoodToId/{id}, addMood, removeMood, learn | |
| 107 | +| `Dimmer` | On, Off, setBrightness/{0-100} | |
| 108 | +| `ColorPickerV2` | color/{hsv\|lxt}, setFavoriteColor/{index}, setSaturation/{val} | |
| 109 | +| `IRoomControllerV2` | setOperatingMode/{0-5}, setComfortTemperature/{°C}, override/{mode}/{secs} | |
| 110 | +| `Switch` / `Pushbutton` | On, Off, pulse, pulse/{durationMs} | |
| 111 | +| `Alarm` | arm, disarm/{code}, delayedOn/{secs}, acknowledge | |
| 112 | +| `Gate` / `GarageDoor` | open, close, stop | |
| 113 | +| `DoorLock` | lock, unlock | |
| 114 | +| `Counter` | reset, set/{value} | |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +## APIs 2 & 3: Loxone App ↔ Loxone AudioServer and Loxone MiniServer ↔ Loxone AudioServer (`openapi.yaml`) |
| 119 | + |
| 120 | +The AudioServer runs **two HTTP + WebSocket servers** that share identical route handling. The App connects to one port; the MiniServer |
| 121 | +connects to the other. |
| 122 | + |
| 123 | +| Server | Env var | Default port | Client | |
| 124 | +| --------- | ------------------------ | ------------ | ----------------- | |
| 125 | +| `appHttp` | `LOXONE_APP_PORT` | **7091** | Loxone App | |
| 126 | +| `msHttp` | `LOXONE_MINISERVER_PORT` | **7090** | Loxone MiniServer | |
| 127 | + |
| 128 | +The WebSocket **identification string** sent on connect differs: |
| 129 | + |
| 130 | +``` |
| 131 | +App: "LWSS V 16.1.10.01 | ~API:1.6~ | Session-Token: …" |
| 132 | +MiniServer: "MINISERVER V LWSS V 16.1.10.01 {mac} | ~API:1.6~ | Session-Token: …" |
| 133 | +``` |
| 134 | + |
| 135 | +When the **MiniServer** WebSocket connects, the AudioServer also emits a **server heartbeat** with current zone state — the App does not |
| 136 | +trigger this. |
| 137 | + |
| 138 | +### Shared commands (App + MiniServer) |
| 139 | + |
| 140 | +Both clients use these. All are HTTP GET; parameters are slash-separated. |
| 141 | + |
| 142 | +``` |
| 143 | +# Zone control |
| 144 | +GET /audio/{zoneId}/play |
| 145 | +GET /audio/{zoneId}/pause |
| 146 | +GET /audio/{zoneId}/volume/{0-100} |
| 147 | +GET /audio/{zoneId}/shuffle/{enable|disable} |
| 148 | +GET /audio/{zoneId}/repeat/{0|1|3} |
| 149 | +GET /audio/{zoneId}/queueplus |
| 150 | +GET /audio/{zoneId}/queueminus |
| 151 | +GET /audio/{zoneId}/position/{seconds} |
| 152 | +GET /audio/{zoneId}/status |
| 153 | +GET /audio/{zoneId}/getqueue/{start}/{limit} |
| 154 | +GET /audio/{zoneId}/serviceplay/{service}/{user}/{audiopath} |
| 155 | +GET /audio/{zoneId}/roomfav/play/{slot} |
| 156 | +
|
| 157 | +# Config / management |
| 158 | +GET /audio/cfg/getservices |
| 159 | +GET /audio/cfg/getplaylists2 |
| 160 | +GET /audio/cfg/getroomfavs/{playerId} |
| 161 | +GET /audio/cfg/search/{service}/{user}/{tag}/{query}/{limit}/{offset} |
| 162 | +GET /audio/cfg/globalsearch |
| 163 | +GET /audio/cfg/spotifyconnect/enable|disable |
| 164 | +GET /audio/cfg/airplay/enable|disable |
| 165 | +GET /audio/cfg/tunein/enable|disable |
| 166 | +… (95+ shared endpoints total — see openapi.yaml) |
| 167 | +``` |
| 168 | + |
| 169 | +### MiniServer-only commands |
| 170 | + |
| 171 | +These are sent **only by the MiniServer** on its dedicated port — the App never sends them. |
| 172 | + |
| 173 | +#### Pairing & Auth (`secure/…`) |
| 174 | + |
| 175 | +Sent immediately after WebSocket connect as a bootstrap handshake: |
| 176 | + |
| 177 | +| Command | Purpose | |
| 178 | +| -------------------------- | --------------------------------- | |
| 179 | +| `secure/info/pairing` | Query paired MiniServer serial | |
| 180 | +| `secure/hello/{publicKey}` | Key-exchange bootstrap | |
| 181 | +| `secure/authenticate` | Auth acknowledgement | |
| 182 | +| `secure/init` | Session init (returns legacy JWT) | |
| 183 | + |
| 184 | +#### Config Sync (`audio/cfg/…`) |
| 185 | + |
| 186 | +Sent by the MiniServer to push or pull configuration state: |
| 187 | + |
| 188 | +| Command | Purpose | |
| 189 | +| -------------------------------------- | ----------------------------------------------------------- | |
| 190 | +| `audio/cfg/ready` | MiniServer signals startup complete; gets session timestamp | |
| 191 | +| `audio/cfg/getconfig` | Fetch config CRC-32 to detect stale cache | |
| 192 | +| `audio/cfg/setconfig` | Push zone UUIDs and names to AudioServer | |
| 193 | +| `audio/cfg/setconfigtimestamp` | Sync config version timestamp | |
| 194 | +| `audio/cfg/miniservertime` | Sync wall-clock time | |
| 195 | +| `audio/cfg/volumes` | Bulk-set initial zone volumes | |
| 196 | +| `audio/cfg/playername` | Set zone display name | |
| 197 | +| `audio/cfg/identify` | Send MiniServer serial + firmware version | |
| 198 | +| `audio/cfg/defaultvolume/{zone}/{vol}` | Set startup volume | |
| 199 | +| `audio/cfg/maxvolume/{zone}/{vol}` | Set volume cap | |
| 200 | +| `audio/cfg/eventvolumes/{zone}/{vol}` | Set alert volume | |
| 201 | +| `audio/cfg/miniserverip` | _(placeholder — ignored)_ | |
| 202 | +| `audio/cfg/miniserverversion` | _(placeholder — ignored)_ | |
| 203 | +| `audio/cfg/timezone` | _(placeholder — ignored)_ | |
| 204 | +| `audio/cfg/presencemode` | _(placeholder — ignored)_ | |
| 205 | +| `audio/cfg/groupopts` | _(placeholder — ignored)_ | |
| 206 | +| `audio/cfg/speakertype` | _(placeholder — ignored)_ | |
| 207 | +| `audio/cfg/playeropts` | _(placeholder — ignored)_ | |
| 208 | + |
| 209 | +### Response format (both clients) |
| 210 | + |
| 211 | +```json |
| 212 | +{ "{command}_result": <payload>, "command": "<original-path>" } |
| 213 | +``` |
| 214 | + |
| 215 | +### WebSocket events (server → all connected clients) |
| 216 | + |
| 217 | +| Event key | Trigger | |
| 218 | +| ----------------------------- | ----------------------- | |
| 219 | +| `audio_event` | Zone state changed | |
| 220 | +| `audio_queue_event` | Queue contents changed | |
| 221 | +| `roomfavchanged_event` | Room favorites changed | |
| 222 | +| `recentlyplayedchanged_event` | Recently played changed | |
| 223 | +| `rescan_event` | Library scan progress | |
| 224 | +| `globalsearch_result` | Search result ready | |
| 225 | +| `audio_sync_event` | Sync group changed | |
| 226 | +| `lineinchanged_event` | Line-in inputs changed | |
| 227 | + |
| 228 | +--- |
| 229 | + |
| 230 | +## API 4: Lox AudioServer Admin UI ↔ Loxone AudioServer (`audioserver-admin-api.yaml`) |
| 231 | + |
| 232 | +Internal REST API used by the AudioServer's browser-based Admin UI. |
| 233 | + |
| 234 | +**Base URL:** `http://{host}:7091` **Auth:** Cookie session via `POST /admin/api/auth/login` (TTL 12 h) |
| 235 | + |
| 236 | +| Group | Base path | Notes | |
| 237 | +| ------------------- | ----------------------------------------------------------- | -------------------------------------------------------------- | |
| 238 | +| Auth | `/admin/api/auth/…` | login, logout, me | |
| 239 | +| Server info | `/admin/api/info` | public, no auth | |
| 240 | +| Zone admin states | `/admin/api/zones/…` | tech details, bulk purge | |
| 241 | +| Zone groups | `/admin/api/groups` | group listing | |
| 242 | +| Transport discovery | `/admin/api/transports/…` | AirPlay, Cast, DLNA, Sonos, Snapcast, Spotify, Music Assistant | |
| 243 | +| Snapcast clients | `/admin/api/snapcast/…` | stream assignment | |
| 244 | +| Spotify | `/admin/api/spotify/…` | OAuth, librespot, bridges | |
| 245 | +| Apple Music | `/admin/api/applemusic/…` | Widevine key upload | |
| 246 | +| Line-In bridges | `/api/linein/…` | bridge register/status/ingest | |
| 247 | +| Audio streams | `/streams/{zone}/{id}.{format}` | mp3/wav/pcm/aac/flac | |
| 248 | +| Media library | `/admin/api/content/library/…` | upload, rescan, storages | |
| 249 | +| Custom radio | `/admin/api/content/radio/…` | add/delete/validate | |
| 250 | +| Logs | `/admin/api/logs/…` | list, SSE stream, level control | |
| 251 | +| Config | `/admin/api/config/…` | CRUD for server config | |
| 252 | +| Alert sounds | `/admin/api/alerts/…` | upload, list, revert | |
| 253 | +| Updates | `/admin/api/adminui/update`, `/admin/api/components/update` | | |
| 254 | + |
| 255 | +--- |
| 256 | + |
| 257 | +## Viewing the docs |
| 258 | + |
| 259 | +**Online:** Visit the [GitHub Pages Swagger UI](https://mr-manuel.github.io/Loxone_api_documentation/) to browse the docs without any local setup. |
| 260 | + |
| 261 | +**Locally:** Open `index.html` in a browser. It loads the YAML specs locally via [Swagger UI](https://swagger.io/tools/swagger-ui/) (fetched from |
| 262 | +unpkg.com — requires internet). |
| 263 | + |
| 264 | +The landing page has **three tabs**: |
| 265 | + |
| 266 | +| Tab | Spec file | Covers | |
| 267 | +| -------------------------------------- | ---------------------------- | ------------------- | |
| 268 | +| **MiniServer API** | `miniserver-api.yaml` | API 1 | |
| 269 | +| **AudioServer – App & MiniServer API** | `openapi.yaml` | APIs 2 & 3 combined | |
| 270 | +| **AudioServer – Admin API** | `audioserver-admin-api.yaml` | API 4 | |
| 271 | + |
| 272 | +In the **App & MiniServer API** tab, routes are grouped by tag: |
| 273 | + |
| 274 | +- Tags **without a prefix** → used by both App and MiniServer |
| 275 | +- Tags starting with **`MiniServer ›`** → MiniServer only |
| 276 | + |
| 277 | +--- |
| 278 | + |
| 279 | +## Files |
| 280 | + |
| 281 | +``` |
| 282 | +api-docs/ |
| 283 | +├── index.html # Swagger UI with 3-tab spec switcher |
| 284 | +├── openapi.yaml # APIs 2 & 3: App + MiniServer audio protocol |
| 285 | +├── miniserver-api.yaml # API 1: MiniServer smart home commands |
| 286 | +├── audioserver-admin-api.yaml # API 4: AudioServer Admin REST API |
| 287 | +└── README.md # This file |
| 288 | +``` |
| 289 | + |
| 290 | +## Sources |
| 291 | + |
| 292 | +| File / directory | Used for | |
| 293 | +| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | |
| 294 | +| `ftp://<loxone-miniserver>:21/web/commonv2.agz` | MiniServer control types, `audio/…` schemas, WebSocket events | |
| 295 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/commands/router/routeRegistry.ts` | Exact route list for both App and MiniServer | |
| 296 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/http/loxoneHttpService.ts` | Two-port server architecture, heartbeat behaviour | |
| 297 | +| `https://github.com/lox-audioserver/lox-audioserver/src/config/loxone.ts` | Port names (`appHttp`/`msHttp`), identification strings | |
| 298 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/loxone/commands/handlers/` | Handler logic, MiniServer-only config sync | |
| 299 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/adminApi/adminApiHandler.ts` | Admin API routes | |
| 300 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/streams/` | Audio stream and proxy endpoints | |
| 301 | +| `https://github.com/lox-audioserver/lox-audioserver/src/adapters/http/lineInApi/` | Line-In bridge API | |
0 commit comments