ENPLDEPT
Flash
All docs
API & protocols

Sensmos API & protocols — node HTTP API, MQTT, software nodes, LoRa frame format

The node speaks plain HTTP on your LAN, MQTT to your broker, and a small binary frame over LoRa. The backend takes data from anything that can POST JSON.

Node HTTP API (LAN, port 80)

Authentication: Authorization: Bearer <PIN> (the PIN you set when adding the node). GET /info is open.

EndpointPurpose
GET /infoid, firmware, alias, uptime, lora{board,role,rx_key,open} when a radio is present
GET /data/statuscurrent entities (own / public / subscribed / diagnostics)
POST /data {entity_id,value,unit}write an entity (own. / native pub.)
GET /data/nativecatalogue of native entities the firmware knows
GET/POST /node/mqttlocal broker configuration
GET/POST /node/lora_emergemergency entity set + command webhook
GET/POST /node/lora_rx {key,open}LoRa receive key phrase (never returned) and plain-frame opt-in
POST /node/lorasend {dst,sub,payload,aes,via?}transmit a LoRa DATA frame (own sensor: radio; other node: via the network)
GET /lora/inboxLoRa inbox: cmds (emergency commands) and frames (sensor frames, via rf/ws/tx)
GET /lora/lastradio state incl. TX diagnostics (link)
POST /node/aliasnode label (shown in /info)
POST /node/pair / DELETEremote-access pairing key (LAN only, by design)

MQTT

Topic root sensmos/<id8>/. Status, diagnostics and entities are published with Home Assistant discovery. Messages arrive on sensmos/<id8>/msg as {from, eid, p} — e.g. {"from":"lora","eid":"lora_frame.3","p":"21.5"} for a LoRa frame from sensor 3, {"from":"owner","eid":"lora_cmd","p":"water_off"} for an emergency command.

Software nodes — data without hardware

POST https://api.sensmos.com/v1/ingest with {key, entities:[{entity_id,value,unit}], lat?, lon?, label?} puts your data on the live map as a software node (Home Assistant, ESPHome, a script — anything). Key ≥ 32 characters is your identity. Software nodes are data-only: they do not earn and are not probe targets.

LoRa DATA frame (for sensor builders)

plain:      [0xE0][0x02][flags][dst 4B][sub 1B][payload 1..128 B][CRC32 LE]
encrypted:  [0xE0][0x02][flags][dst 4B][sub 1B][nonce 4B][AES-256-CTR(payload+CRC32)]
  • dst = first 4 bytes of the target node's id (the 8-hex id shown in the app); sub = your sensor's number behind that node (1–255).
  • flags bit0 = AES; bit1 = "last hop" — set only by the node when it transmits to a sensor. A sensor accepts a frame only when bit1 is set, dst is its node and sub is its own; it sends its own uplinks with bit1 = 0.
  • CRC32 (zlib) of the payload, little-endian, inside the encrypted block — a wrong key fails the CRC.
  • AES-256-CTR, key = SHA-256(key phrase), counter block [nonce 4B][dst 4B][sub][0…], random nonce per frame.
  • Radio: EU 868.1 MHz / US 903.9 MHz, BW 125 kHz, SF11, CR 4/5, sync word 0x34, LoRa CRC on, normal IQ. Listen before talk; respect the 1% duty cycle (≈25 frames/hour at 30 bytes).
  • One radio hop only. Uplinks are heard by any node or gateway and delivered to the target node over the network; downlinks to a sensor are transmitted only by that sensor's own node.

An actuator that never transmits is invisible to the network — send a short "hello" once an hour so the network learns who hears it. The full engineering contract lives in the firmware repository (DOCS/dev/LORA-MESSAGING.md).

Gateways as listeners

A LoRaWAN gateway you run (Semtech packet forwarder) can hear for the whole network: a passive agent forwards every received frame to the backend under your node's identity — setup on LoRaWAN gateway, the contract (pairing, tokens, formats) on Attachments & the EXT API. Frames for Sensmos nodes are routed; foreign LoRaWAN traffic is only counted as spectrum statistics.

Last updated: 2026-09-03