Device HTTP API
The device-facing contract: one bearer token, three endpoints. Works from firmware, scripts, gateways — anything that speaks HTTPS.
Authentication
Every request carries the device token issued at provisioning: Authorization: Bearer <device_token>. Tokens are stored hashed and shown once; rotate from the device page (POST /v1/devices/{id}/rotate-token in the app API) if lost. A rotated token invalidates the old one immediately.
Send telemetry
POST https://api.latticeiot.com/v1/ingest/{device_id}/telemetry
Authorization: Bearer <device_token>
Content-Type: application/json
{"temp_c": 23.4, "humidity": 61, "door": "open", "lamp": true}
→ 202 Accepted- Body is a flat JSON object: metric name → value. Nested objects are dropped per-metric.
- Numbers land as chartable values; strings and booleans are stored as text values.
- A
202means accepted for processing — data is visible in the app within seconds.
Receive commands (long-poll)
GET https://api.latticeiot.com/v1/ingest/{device_id}/commands?wait_s=20
Authorization: Bearer <device_token>
→ 200 OK
{"commands": [
{"id": "4217", "name": "set_lamp", "params": {"on": true},
"issued_at": "2026-07-10T18:04:00Z"}
]}wait_s (max 20) holds the request open until a command arrives or the window elapses, so a simple loop gives near-realtime delivery without keeping sockets open. Undelivered commands queue and are returned on the next poll.
Acknowledge a command
POST https://api.latticeiot.com/v1/ingest/{device_id}/commands/{command_id}/ack
Authorization: Bearer <device_token>
→ 200 OKAck after your device has applied the command. Unacked commands are redelivered on subsequent polls.
Full device loop (Python)
import requests
BASE = "https://api.latticeiot.com"
DEVICE = "YOUR-DEVICE-ID"
HEADERS = {"Authorization": "Bearer YOUR-DEVICE-TOKEN"}
while True:
requests.post(f"{BASE}/v1/ingest/{DEVICE}/telemetry",
json={"temp_c": 23.4}, headers=HEADERS, timeout=10)
r = requests.get(f"{BASE}/v1/ingest/{DEVICE}/commands",
params={"wait_s": 20}, headers=HEADERS, timeout=30)
for cmd in r.json().get("commands", []):
handle(cmd["name"], cmd.get("params")) # your code
requests.post(f"{BASE}/v1/ingest/{DEVICE}/commands/{cmd['id']}/ack",
headers=HEADERS, timeout=10)Errors & limits
| Status | Meaning | What to do |
|---|---|---|
401 | Missing, malformed, or rotated token | Re-check the Authorization header; rotate the token in the app if it was lost. |
413 | Payload too large (16 KB max) | Send fewer metrics per message or shorten text values. |
429 | Rate limit exceeded | Back off for the number of seconds in the Retry-After header, then resume. |
- Rate limit: roughly 300 messages/min per device (5/s with short bursts allowed).
- Up to 64 metrics per message; text values up to 256 chars.
- Plans carry daily message caps; hitting a cap also returns
429withRetry-After.
429 as flow control, not failure: honor Retry-After and keep the loop running. For most sensors, one reading every 5–60 seconds is plenty.Prefer MQTT?
Self-hosted deployments expose the same data plane over MQTT — see MQTT (self-hosted). Hosted MQTT is coming; HTTPS is the recommended path on the hosted platform today.