Provisioning a device
What a QuickComm device carries in non-volatile storage, the QR payload that puts it there, the self-registration call your server can answer, and the reachability check to run before you flash a fleet.
8 min read
On this page
Provisioning is the only moment a device learns who it is and where to send its audio. Get this right and the rest is mechanical.
What the device ends up holding
Everything the device retains across a power cycle is in this table. There is nothing else in non-volatile storage that affects how it behaves.
| Key | Example | Notes |
|---|---|---|
| mac_address | AA:BB:CC:DD:EE:FF | The burned-in MAC, or one you assign at provisioning |
| property_id | 4 | Required. No default, no guess — the device refuses to run without it |
| organization_id | 2 | Carried for convenience |
| team_id or table_id | 7 | Whichever your placement model uses |
| user_id | 19 | Optional — who is carrying it |
| comm_mode | udp | Selects the transport: udp, ai or websocket |
| server host and port | 192.168.1.50:8000 | Your backend |
| ssid / password | — | Wi-Fi credentials |
The QR provisioning payload
Devices ship with a QR provisioning card. The device boots into SoftAP mode and publishes a captive portal at 192.168.4.1; the installer scans the QR with a phone, the phone opens the URL, and the device parses the query string into non-volatile storage and reboots.
http://192.168.4.1/?mac_address=AA:BB:CC:DD:EE:FF
&property_id=4
&organization_id=2
&team_id=7
&user_id=19
&comm_mode=websocket ← udp | ai | websocket
&mode=local ← where the device reports
&local_ip=192.168.1.50
&local_port=8000
&ssid=RestaurantWiFi
&password=...Assigning identity rather than reading it off a sticker
mac_address does not have to be the burned-in one. A MAC you assign at provisioning travels to the unit inside the QR payload and becomes its operating identity for heartbeats, presence, print claiming and everything else.
This exists so an installer never has to transcribe a MAC off a sticker — one transcription error per table is one silently broken table. If your workflow can accept a provisioned identity, use it; it removes an entire class of field error.
Self-registration from the device
A device can announce itself to your server with no operator credential. This is how a factory-flashed unit joins a fleet without anyone creating a record for it first. Implement it if you want devices to be self-serve; return 404 if you would rather every device be pre-registered in your own system.
POST /api/devices/register HTTP/1.1
Content-Type: application/json
{
"mac_address": "AA:BB:CC:DD:EE:FF",
"property_id": 4,
"team_id": 7,
"user_id": 19,
"ip_address": "192.168.1.88",
"communication_mode": "udp"
}Four behaviours worth matching in your own implementation, each learned the hard way:
- Normalise the MAC to AA:BB:CC:DD:EE:FF. Accept colon, dash and bare 12-character hex on the way in, and rewrite anything already stored in another form.
- Make the call idempotent. An existing device is updated, not duplicated.
- Preserve communication_mode if it is omitted. A re-registration that forgot the field used to silently reset udp devices to ai and take them off the air.
- Validate relationships rather than silently correcting them. A team that does not belong to the property is a 4xx, not a quiet reassignment.
A bearer credential for the device's own calls
POST /api/auth/device/token HTTP/1.1
Content-Type: application/json
{ "mac_address": "AA:BB:CC:DD:EE:FF", "ip_address": "192.168.1.88" }
──────────────────────────────────────────────
{ "access_token": "...", "refresh_token": "...",
"token_type": "bearer", "expires_at": "..." }Endpoint reachability — read this before you deploy
The device-facing routes do not all live under one prefix. A reverse proxy configured to forward only /api will publish the fleet plane perfectly and silently swallow every audio, print and notification route.
| Route | Under /api? | Transport |
|---|---|---|
| POST /api/devices/…, POST /api/auth/… | Yes | HTTPS |
| POST /voice, POST /voiceecho | No | HTTPS |
| GET /sse/notifications | No | HTTPS |
| GET /restaurant/print-jobs, POST /restaurant/orders | No | HTTPS |
| wss://<host>/ws | No | WebSocket over TLS |
| udp-v1 audio ingest | No | UDP, default port 12345 |
HOST=https://api.yourcompany.com
MAC=AA:BB:CC:DD:EE:FF
# Fleet plane — must answer 200
curl -s -o /dev/null -w "heartbeat %{http_code}\n" -X POST $HOST/api/devices/heartbeat \
-H "Content-Type: application/json" -d "{\"mac_address\":\"$MAC\"}"
# Mode B downlink — 204 is the healthy empty answer; 404 means unpublished
curl -s -o /dev/null -w "notify %{http_code}\n" $HOST/sse/notifications \
-H "x-device-id: $MAC"
# Mode C print bridge — 204 is healthy and empty
curl -s -o /dev/null -w "print %{http_code}\n" $HOST/restaurant/print-jobs \
-H "x-device-id: $MAC"
# Mode C socket — expect 101 Switching Protocols
curl -s -o /dev/null -w "websocket %{http_code}\n" \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
$HOST/ws
# Mode A ingest — UDP is fire-and-forget, so watch the server as you run this
printf 'v1,1,probe01,%s,,4,,,\n0000000000' "$MAC" | nc -u -w1 api.yourcompany.com 12345Something wrong or missing on this page? Tell us.

