ENPLDEPT
Flash
All docs
Attachments · EXT API

Attachments & the EXT API — pair a LoRaWAN gateway, SDR or other receiver through a Sensmos node

An attachment is any device or program that hears something useful but is not, and will not be, a Sensmos node — a LoRaWAN gateway, an SDR, a receiver. It has no wallet and no attestation. A node on the same LAN vouches for it and the backend issues it a narrow, revocable token.

The trust model

Two barriers, each closing a different door:

barrierproveschecked by
node PINyou are physically on the owner's networkthe node
backend grantthe node exists, has an owner, has a free slotthe backend

Three questions are kept separate on every request:

questioncarried by
who are youtoken (sma_…)
what you may doscopes (rows on the server, not bytes in the token)
what format you sendthe URL

A token is 24 random bytes with an sma_ prefix — it encodes nothing. Permissions live server-side and can be edited or revoked without reissuing anything.

Pairing — how you get a token

All of this happens on the LAN, against the node's local HTTP server (port 80). You need the node's IP and its PIN. (To discover nodes, probe /info across the subnet — whatever answers is a node.)

1 — check the node

GET http://<node-ip>/info
→ 200 { "device_id": "…", "firmware": "1.00", "ws_connected": true }

ws_connected must be true — the node relays your request to the backend over its own link.

2 — request authorization

POST http://<node-ip>/ext/authorize
Authorization: Bearer <PIN>
{ "kind": "lora-gw", "name": "rooftop RAK", "scopes": "radio.frames,radio.spectrum" }

scopes is a comma-separated string, not a JSON array. Responses: 202 {"req":"<id>"} relayed, poll for the answer · 403 wrong PIN · 400 missing kind/scopes.

3 — poll for the grant

GET http://<node-ip>/ext/authorize?req=<id>
Authorization: Bearer <PIN>
→ { "status": "pending" }
→ { "status": "granted", "token": "sma_…", "id": "<attachment-id>", "exp": "<iso>" }
→ { "status": "denied",  "reason": "…" }

Poll once a second; the node keeps the answer for about 5 minutes. Store the token with mode 0600 — from now on you talk to the backend directly and the node is out of the loop.

A granted-but-unused token expires after 30 minutes. Its full lifetime (30 days, renewable) starts at first use. One node vouches for at most four attachments; the owner can revoke any of them from the app, which makes the token inert immediately.

Talking to the backend

Base URL https://sensmos.com/v1/ext, every request with Authorization: Bearer sma_….

callpurpose
GET /v1/ext/meare you still alive, what may you send, which integrations/formats the server accepts (catalog) — call it on startup
POST /v1/ext/renewextend the token's expiry; once a day is plenty
POST /v1/ext/<integration>/<format>send data → 200 {"ok":true,"accepted":n}

Live endpoints:

URLscopebody
/v1/ext/radio/rxpkradio.framesSemtech packet-forwarder PUSH_DATA JSON, as-is
/v1/ext/radio/framesradio.framesnative frame format (below)
/v1/ext/radio/channelradio.spectrumone spectrum reading (below)

Data formats

rxpk — Semtech packet forwarder, one to one. Send the object exactly as the forwarder produced it in PUSH_DATA:

{ "rxpk": [ {
    "time": "2026-08-21T20:48:11Z",
    "freq": 867.3, "datr": "SF9BW125", "modu": "LORA",
    "rssi": -44, "lsnr": 12.0, "size": 38, "stat": 1,
    "data": "4FNNT1MgY2Nk…"
} ] }

datr is parsed only for LoRa; modulation comes from modu. stat follows Semtech (1 CRC OK, -1 fail, 0 none). tmst is ignored; without time the server stamps arrival time.

frames — native format, for attachments written for us (own receiver, SDR, Meshtastic bridge):

{ "frames": [ {
    "ts": 1755808091, "freq": 867.3, "sf": 9, "mode": 0,
    "rssi": -44, "snr": 12.0, "len": 38, "crc": true,
    "hex": "e0534d4f53…", "dir": "rx"
} ] }

mode 0 LoRa / 1 FSK; sf 0 for FSK; hex = PHY payload, first 128 bytes kept. dir: "tx" is for transmissions your gateway makes that others can hear (normal inverted-IQ downlinks prove nothing — skip them); for tx, rssi carries TX power.

channel — one spectrum reading:

{ "freq": 869.618, "bw": 62.5, "sf": 8, "sync": 18, "noise": -118, "peak": -97 }

freq required, the rest defaults to 0; noise/peak in dBm.

Limits and errors

codebodymeaning
401invalid_tokentoken unknown, expired or revoked
403missing_scopetoken valid, but not for this endpoint
400parser messagebody does not match the format
404unknown_endpoint + catalogno such integration/format
413too_manyover the batch cap — 200 frames per request

Batch on your side — once a second, or when 50 frames accumulate, whichever comes first. Application payloads stay encrypted end to end: the server works with radio metadata and recognises Sensmos frames among the traffic; it has no keys for anyone's LoRaWAN payloads and wants none.

What the data does

Frames land in the same tables and on the same map as frames heard by a node's own radio — an attachment is not a separate being, it is a better antenna for the node that vouched for it. Sensmos emergency broadcasts and sensor frames are recognised and delivered to their owners; verified Sensmos beacons build radio-coverage edges between nodes; the vouching node's data profile gains the RF category.

Ready-made attachments: LoRaWAN gateway (passive listener). Source for both agents is on GitHub.

Adding a new integration type

The server side of every integration is one declarative entry in a registry (scopes, formats, parser, handler). Candidates on the same pattern, none of them live yet: GNSS sky view and interference (gnss.sky, deliberately without position), ADS-B and AIS reception summaries per window, rtl_433, power meters. If you have hardware and want a format accepted, get in touch on Discord — a format entry is a small, reviewable change.

Questions, straight answers

Which attachments exist today?

LoRaWAN gateways — the passive listener for Semtech packet forwarders (tested, running on foreign gateways) and an agent for ChirpStack Gateway Bridge over MQTT (complete but untested — no such gateway to verify on). Both report received radio frames; everything else on this page is the contract they use, open to anyone with hardware.

Do attachments earn?

No. They are data-only; the node that vouches for them is the only earning identity. What they hear is delivered exactly like frames heard by a node's own radio.

How many attachments per node?

Four. The owner sees and revokes them in the app under the node.

Can an attachment do anything on my LAN?

Pairing only happens against the node's local API with the node's PIN, from inside the LAN. The backend never reaches into the LAN; a token only allows posting data to /v1/ext.

Last updated: 2026-09-03