How to build Signal integration through cloud API

Step-by-step guide to controlling a Luxafor Signal device from any script, app, or service using share codes and our cloud API.

Heads up — public API in progress. The Signal cloud API and its documentation are still being finalised, so small changes may happen between now and general availability. This article will be kept up to date; the OpenAPI spec linked at the end is always the source of truth.


In this article

  • Before you start
  • How Signal control actually works
  • What a share code is, in plain language
  • Step 1 — Generate and redeem your share code
  • Step 2 — Confirm what your account can see
  • Step 3 — Check that a device is online
  • Step 4 — Send a command to the device
  • Step 5 — Give up access when you no longer need it
  • The command vocabulary
  • Worked example — turn the light red, then play a sound
  • Integration checklist
  • Where to look next
  • Need more help?

Before you start

You need three things ready before you write a single line of integration code:

  1. A configured Signal device. Set it up once over USB through luxafor.app and make sure it connects to your Wi-Fi. Full instructions are in our Signal setup guide.
  2. Any audio files you plan to play, already uploaded to the device. Audio cannot be streamed over the API — it has to live on the device's onboard storage. See Adding audio files to your Signal.
  3. A share code, generated from the dashboard. Section Step 1 below walks through this.

You do not need prior programming experience to follow the conceptual sections of this article. To actually call the API you'll need to be comfortable making HTTP requests with curl   , Postman, or your language of choice.


How Signal control actually works

Every command you send travels through three pieces:

  • Your integration — any script, app, or service holding a valid access token.
  • The Luxafor cloud at services.luxafor.io    — keeps track of devices, accounts, and access permissions.
  • The Signal device itself, identified by a deviceId    string.

Your script ──HTTP──▶ Luxafor cloud ──Wi-Fi──▶ Signal device

You never connect to the device's IP address directly. You send commands to a URL on services.luxafor.io   , the cloud delivers them to the device over Wi-Fi, and the device executes them.

Important. Commands sent to a disconnected device are silently dropped — the cloud does not queue them for later. Always check device status (Step 3) before time-sensitive alerts.


What a share code is, in plain language

A share code is a short, single-use token that grants another party — another browser, another app, your own integration — the ability to control a Signal device that has already been configured.

It's how Luxafor hands control of a device to something other than the original configurator, without exposing usernames, passwords, or device serial numbers.

The lifecycle:

  1. You configure your Signal once at luxafor.app over USB.
  2. From the dashboard, you generate a share code starting with share_   .
  3. You redeem that code from your integration. The cloud returns an accessToken    and a deviceId.
  4. From then on, your integration uses the access token to send commands.

Each share code is meant to be redeemed once, fairly soon after it is generated.

If you lose the access token, treat your integration as a fresh installation: generate a new share code from the dashboard and redeem it without an Authorization    header. The response will include a brand-new access token tied to a brand-new account.


Step 1 — Generate and redeem your share code

Generate the share code

  1. Open luxafor.app and sign in.
  2. Find the device you want the integration to control on the dashboard.
  3. Open the device's view and click Create share code.
  4. A popup appears with a short string beginning with share_   . Copy it.

If your integration is something a user installs, this is the value they paste into your setup screen. If you're building a personal integration, paste it directly into your script's configuration.

Redeem the share code

Send the code to the registry with a POST    request. This endpoint has two modes, and the difference matters:

Mode A — First redemption (no existing account)

Send the request without an Authorization    header. The registry creates a brand-new account, attaches the device to it, and returns an accessToken    you'll use for every subsequent call.

http
POST https://services.luxafor.io/signal/registry/v1/shares/redeem Content-Type: application/json  { "code": "share_aBc123XyZ" }

Successful response:

json
{   "deviceId": "dev_5f7c9e2b",   "userId": "usr_8a1d4b6e",   "accessToken": "lxr_tk_91c8f4a2b6e7d3..." }

Save the access token immediately. The registry will never return it again. If you lose it, you have to start over with a fresh share code.

Mode B — Adding another device to the same account

Include your existing access token in the Authorization    header. The registry attaches the new device to the account that token already represents.

http
POST https://services.luxafor.io/signal/registry/v1/shares/redeem Content-Type: application/json Authorization: Bearer lxr_tk_91c8f4a2b6e7d3...  { "code": "share_dEf456WvU" }

Successful response (no accessToken    field, because you already have one):

json
{   "deviceId": "dev_d2a4b8c1",   "userId": "usr_8a1d4b6e" }

Notice the userId    matches the value from your earlier redemption — the new device is sitting next to the old one on the same account.

What you should have after Step 1

Persisted somewhere safe — environment variable, secrets manager, encrypted database column — you should now have:

  • One accessToken   
  • One or more deviceId    strings

Everything from here uses them.


Step 2 — Confirm what your account can see

This step is optional but useful for sanity-checking Step 1 and for rebuilding state when your local database has drifted out of sync with the registry.

http
GET https://services.luxafor.io/signal/registry/v1/users/me Authorization: Bearer lxr_tk_91c8f4a2b6e7d3...

Successful response — every device attached to the account in one list:

json
{   "uid": "usr_8a1d4b6e",   "devices": ["dev_5f7c9e2b", "dev_d2a4b8c1"] }

The uid    value is the same userId    that Step 1 returned. If a device you expected to see is missing, it usually means the redemption in Step 1 went to a different account than you thought — typically because the wrong access token (or no token) was sent.


Step 3 — Check that a device is online

Before sending commands, you usually want to know whether the device is currently reachable over Wi-Fi. This is especially important before user-facing notifications or alerts.

http
GET https://services.luxafor.io/signal/registry/v1/devices/dev_5f7c9e2b Authorization: Bearer lxr_tk_91c8f4a2b6e7d3...

Successful response:

json
{   "id": "dev_5f7c9e2b",   "connected": true }

If connected    is false   , the device is offline — typically powered off, out of Wi-Fi range, or waiting for credentials to be re-entered through the Luxafor web app. There is nothing the API can do about that; the device has to come back online on its own. Depending on your integration, you may want to surface a warning to the user, retry later, or fail loudly.

Checking your permissions on a device (optional)

Most integrations don't need this — it exists for setups where a device is shared with multiple parties at different access levels.

http
GET https://services.luxafor.io/signal/registry/v1/devices/dev_5f7c9e2b/membership Authorization: Bearer lxr_tk_91c8f4a2b6e7d3...

Successful response:

json
{ "role": "owner" }

Step 4 — Send a command to the device

This is the step that actually drives the light. You POST    one command at a time to the command service. The body is a JSON object with a single command    field, whose value is one of the AT-style strings described in The command vocabulary.

http
POST https://services.luxafor.io/signal/api/device/dev_5f7c9e2b/commands Authorization: Bearer lxr_tk_91c8f4a2b6e7d3... Content-Type: application/json  { "command": "AT+SC=4095,FF0000" }

A successful response is a 2xx    status with an empty body. The bitmask 4095    selects all 12 LEDs on the current hardware variant — the example above turns the whole ring solid red. (Older 11-LED variants use 2047   .)

Two things worth knowing about this endpoint

  1. The path prefix is /signal/api   , not /signal/registry/v1   . The command service is a sibling of the registry, running on the same host. It is not described in the registry's OpenAPI document, but it is still mandatory for any integration that actually drives the light.
  2. No batch endpoint. If you need to do several things in a row — set a color, set the volume, play a sound — send one request per command in the order you want them to take effect. The worked example below shows this.

The same Authorization: Bearer <accessToken>    you obtained in Step 1 works here. There is no separate token for the command service.


Step 5 — Give up access when you no longer need it

When your integration no longer needs the device — a customer uninstalls your app, the device is removed from your admin dashboard, or you're cleaning up after a test — let the registry know by deleting your own membership.

http
DELETE https://services.luxafor.io/signal/registry/v1/devices/dev_5f7c9e2b/users/me Authorization: Bearer lxr_tk_91c8f4a2b6e7d3...

A successful call returns 204 No Content   : status code 204, empty body.

After this, the device is no longer attached to your account, and any other endpoint you call for that deviceId    with the same access token will return 401    or 403   .

The device itself is not affected. It stays configured on the original owner's account, and the owner can re-share it at any time by generating a new share code.

If your access token starts returning 401 or 403 on every call without your having issued this DELETE yourself, the user has probably revoked the share or deleted their account on the Luxafor side. Stop using the stored token, ask the user for a fresh share code, and start again from Step 1 in Mode A.


The command vocabulary

Commands sent through the /commands    endpoint use a small text protocol inspired by the AT command set used in modems. Each command is sent as a string inside { "command": "..." }   .

Solid color

AT+SC=<ledBits>,RRGGBB
  • <ledBits>   is a bitmask selecting which LEDs to light: each bit corresponds to one LED in the ring.
    • Current Signal hardware has 12 LEDs, so the "all on" value is 4095    (binary 111111111111   ).
    • Older units have 11 LEDs and use 2047    (binary 11111111111   ).
    • If your integration may run against both variants, store the LED count per device and compute the bitmask: 2^n − 1    for n    LEDs.
  • RRGGBB   is a hexadecimal color without the #    prefix.

Examples:

Goal Command
All 12 LEDs calm blue AT+SC=4095,3366FF   
All 12 LEDs solid red AT+SC=4095,FF0000   
First 5 LEDs blue AT+SC=31,0000FF   

Built-in patterns

The device firmware ships with 25 built-in patterns, each referenced by a numeric ID from 0 to 24. Triggering one is a two-command sequence — first select the pattern with AT+DP   , then start playback with AT+PP   :

AT+DP=<patternId> AT+PP=<enable>,<repeatCount>
  • <enable>    is 1    to start, 0    to stop.
  • <repeatCount>    is between 1    and 255   . The value 255    means "play indefinitely until stopped."

Pattern catalog (firmware 1.0):

ID Pattern ID Pattern ID Pattern
0 Police 9 Red Running 18 Blue Pulses
1 Traffic Lights 10 Green Running 19 Yellow Pulses
2 Red Flashes 11 Blue Running 20 Magenta Pulses
3 Green Flashes 12 Yellow Running 21 Cyan Pulses
4 Blue Flashes 13 Magenta Running 22 White Pulses
5 Yellow Flashes 14 Cyan Running 23 Rainbow
6 Magenta Flashes 15 White Running 24 Alert
7 Cyan Flashes 16 Red Pulses

8 White Flashes 17 Green Pulses

If you need the authoritative list at runtime — for example, to populate a dropdown in your UI — the device returns it in response to AT+GDPL   .

Example — play the police pattern five times:

AT+DP=0 AT+PP=1,5

Audio playback

Audio files have to live on the device itself before they can be played. There is no API for streaming audio. To get a file onto a device, open the device's settings at luxafor.app and use the file upload control.

The device ships with seven pre-loaded sound effects. You can add additional MP3 files until on-device storage runs out — the maximum file size is governed by free storage, not a fixed limit (though the web app may enforce its own limit at upload time).

Once a file is on the device, play it with:

AT+VOL=<0-100> AT+PLAY=<filename>
  • AT+VOL   takes a percentage from 0 to 100 (firmware default is 20). It's optional — if you don't send it, the device uses its current volume, which persists across reboots.
  • AT+PLAY   also accepts an optional repeat count: AT+PLAY=<filename>,<repeatCount>   , where <repeatCount>    is between 1 and 255.
  • To stop playback before it finishes, send AT+STOP   .

Worked example — turn the light red, then play a sound

This assumes you have already redeemed a share code and saved the resulting accessToken    and deviceId   .

Request 1 — Set the light to solid red:

http
POST https://services.luxafor.io/signal/api/device/{deviceId}/commands Authorization: Bearer <accessToken> Content-Type: application/json  { "command": "AT+SC=4095,FF0000" }

Request 2 — Set the volume to 60%:

http
POST https://services.luxafor.io/signal/api/device/{deviceId}/commands Authorization: Bearer <accessToken> Content-Type: application/json  { "command": "AT+VOL=60" }

Request 3 — Play the alert sound:

http
POST https://services.luxafor.io/signal/api/device/{deviceId}/commands Authorization: Bearer <accessToken> Content-Type: application/json  { "command": "AT+PLAY=alert.mp3" }

Each command is a separate HTTP request. Send them in the order you want them to take effect.

Equivalent curl    call for the first request

bash
curl -X POST \   "https://services.luxafor.io/signal/api/device/<DEVICE_ID>/commands" \   -H "Authorization: Bearer <ACCESS_TOKEN>" \   -H "Content-Type: application/json" \   -d '{"command":"AT+SC=4095,FF0000"}'

Integration checklist

The complete surface area required to drive a Signal device end-to-end:

  1. Configure the device once through luxafor.app using a USB cable. Confirm it can be controlled from the dashboard.
  2. Upload any audio files you intend to use from the same dashboard. MP3, under 1 MB each.
  3. Generate a share code from the dashboard for each integration you build.
  4. Redeem the share code with POST /signal/registry/v1/shares/redeem   . Save the accessToken    and deviceId    somewhere safe — environment variable, secrets manager, or an encrypted column in your application's database.
  5. (Optional) Confirm the device is online with GET /signal/registry/v1/devices/{deviceId}    before each command burst.
  6. Send commands to POST /signal/api/device/{deviceId}/commands    using the AT command strings from the vocabulary section.
  7. Handle 401   /403    responses by treating the access token as invalid and prompting the user to generate a new share code.

That's the whole thing.


Where to look next


Need more help?

If something in this article isn't working as expected, or you're building an integration that needs a capability you don't see here, get in touch:

(Mon–Fri, 09:00–17:30 EET)

When you write in, please include your deviceId   , the exact HTTP request you sent, and the response you got back. That triages an integration question much faster than a screenshot.

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us