Device Registration & Claim
:::info Status
Implemented. Source: docs/transmitter-mqtt-contract.md §10, docs/user-device-mqtt-contract.md §9.
:::
Registration is REST, not MQTT — this page documents how a unit goes from "unprovisioned hardware" to "holds broker credentials and connects per the topic structure". The credential itself is what lets everything else in this protocol reference happen.
All flows end in the same terminal state: the device holds
{device_id, mosque_id, broker credentials} in NVS and connects. Until
provisioned, a device exposes a local setup mode (Wi-Fi AP + captive
portal).
Transmitter: flows A/B/C — registration + claim
- Supplying
serial_numberat registration makes this flow C; omitting it (flow A/B) mints a synthetic serial (SYNTH-TX-{uuid}) so every transmitter has a manifest row. Flow B's QR pairing is the same call — the panel renders the setup code as a QR instead of text. - The claim response is the broker credential — there is no separate secret-issuance step.
broadcast_secretis unrelated to the MQTT fields beside it — it authenticates the audio uplink WebSocket instead, and is returned once, only when this claim freshly minted it (a re-claim of an already-secreted unit getsnull— usePOST /{device_id}/regenerate-broadcast-secretif needed again).
Transmitter: flow D — auto-registration with approval
{"state": "unknown"} is a normal outcome, not an error. None of flow D is
a mosque-admin action — it's Super-Admin-only end to end; the Admin Panel's
"Unclaimed devices" card shows these read-only, telling a mosque admin to
contact the platform team rather than letting them approve directly.
Stream player: flows A/C/D — mirrors the transmitter, with differences
Identical mechanics to the transmitter's flows A, C, D, with three
differences (source: docs/user-device-mqtt-contract.md §9):
- The claim endpoint is
POST /api/v1/user-devices/claim, bindinguser_id(the buying member) in addition tomosque_id. - The setup AP is
Azan360-RX-{last4 of MAC}. - Claim does not grant streaming. The claim response returns broker
credentials and
device_id; the registry row landsPENDINGawaiting Super Admin authorization. The device connects, publishes status, is visible to Super Admin — butstream_urlis withheld and noplayis sent untilACTIVE. Until then it publishesONLINEand plays nothing over Azan360 (Bluetooth is unaffected — see Stream Player module).
Critically: the unit stays subscribed to config and cmd the entire
time it is UNBOUND. Approval is a single retained-config publish onto
that open session, taking effect in under a second. The unit must never
poll for its authorization state after claim — no /announce loop, no
periodic sync_config, no reconnect cycle to "check". The backend contract
is that approval is instant.
A member who buys several units repeats this per unit — no bulk
inheritance: unit two starts PENDING even if unit one is already
authorized.
Rebinding is not a provisioning flow
Once claimed and approved, moving a stream player to another mosque is an
ordinary config update: the owner picks a mosque they've joined, and the
backend publishes a new config with the new mosque_id and stream_url.
No setup code, no claim call, no credential change, no topic change, no
reboot. See Device Lifecycle.
Admin lifecycle actions (support tooling)
None of these add new device-facing topics or payload fields — transmitter
side, all on /api/v1/mosque-devices/{device_id}:
| Action | Endpoint | Effect |
|---|---|---|
| Force-claim | POST /{id}/force-claim | Superadmin: marks registered but unclaimed as claimed, skipping setup-code/MAC verification |
| Regenerate setup code | POST /{id}/regenerate-setup-code | Fresh one-time code, 24h expiry |
| Regenerate broadcast secret | POST /{id}/regenerate-broadcast-secret | Superadmin: rotates the audio-uplink secret |
| Maintenance | POST /{id}/maintenance {enabled, reason?} | Pause/resume |
| Factory reset | POST /{id}/factory-reset | Wipes claim state; unit re-provisions |
| Identity edit | PATCH /{id} {serial_number?, mac_address?, mqtt_client_id?} | Superadmin-only |
| Retire/delete | DELETE /{id} | Soft-retires a claimed device; hard-deletes a never-claimed row |
Full schemas: API Reference. Broader lifecycle model (identity/ownership/authorization/binding as separate concepts): see Concepts and Device Lifecycle.