Skip to main content

HTTP API

The HTTP API suits devices that cannot hold a connection open, networks that allow only ordinary HTTPS, and firmware bring-up — because unlike MQTT, every request answers with what the platform made of each reading you sent.

Base URL

https://api.sensocan.com/api/v1

Authentication

Every request carries the device's access token as a bearer token:

Authorization: Bearer {DEVICE_ACCESS_TOKEN}

Get the token from Device DetailsShow Token. The token belongs to one device and the URL you post to must be that same device's UUID.

Device Details after pressing Show Token: the device token dialog open with the access token visible and its copy button, so the reader can see where the Bearer token comes from.
Device Details after pressing Show Token: the device token dialog open with the access token visible and its copy button, so the reader can see where the Bearer token comes from.
Regenerating the token locks out the device

The regenerate action in the same dialog issues a new token and invalidates the old one at once. Every device using the old token starts getting 401 until you flash the new one.

Headers

HeaderValueRequired
AuthorizationBearer {DEVICE_ACCESS_TOKEN}Yes
Content-Typeapplication/jsonYes
Acceptapplication/jsonRecommended — without it, the rate-limit response comes back as an HTML page instead of JSON

Endpoints

1. One reading for one sensor

POST /devices/{device_uuid}/sensors/{sensor}/readings

{sensor} is either the sensor's slug or its UUID — both are shown on the sensor's page.

{
"data": {
"value": 25.5,
"timestamp": "2026-09-03T10:00:00Z"
},
"battery_voltage": 3.7
}
FieldTypeRequiredNotes
dataobjectYesExactly one reading. Use the bulk endpoint for several
data.valuenumberYesNon-numeric values are rejected
data.timestampISO 8601 stringNoDefaults to the time the request arrives
battery_voltagenumber (volts), 0–100NoSits beside data, not inside it

Any other key inside data is kept as metadata with the reading — see the shared payload rules.

2. Several readings for one sensor

POST /devices/{device_uuid}/sensors/{sensor}/readings/bulk
{
"data": [
{ "value": 22.1, "timestamp": "2026-09-03T09:00:00Z" },
{ "value": 22.4, "timestamp": "2026-09-03T09:15:00Z" },
{ "value": 22.8, "timestamp": "2026-09-03T09:30:00Z" }
],
"battery_voltage": 3.6
}

data must be an array with at least one entry. Each entry follows the same rules as data above.

3. Readings for several sensors

POST /devices/{device_uuid}/readings
{
"data": [
{
"sensor_slug": "temp_01",
"value": 25.5,
"timestamp": "2026-09-03T10:00:00Z"
},
{ "sensor_uuid": "7a8b9c0d-1234-5678-90ab-cdef12345678", "value": 60.2 },
{ "sensor_slug": "door_01", "value": 1 }
],
"battery_voltage": 3.7
}

Each entry names its own sensor with either sensor_slug or sensor_uuid; one of the two is required. Use this endpoint for a device with several sensors — three separate requests do the same work three times.

What the response tells you

A request that got at least one reading through answers 202 Accepted:

{
"message": "Readings accepted and queued for rule chain processing.",
"accepted": 3,
"duplicates": 0,
"rejected": {},
"sensor": "temp_01"
}
FieldMeaning
acceptedReadings handed to the rule chain
duplicatesReadings recognised as repeats of something already submitted. Counted as success — replaying a buffer is safe
rejectedDrop reasons that occurred, with counts. Empty ({}) when nothing was dropped
sensorThe identifier you put in the URL, echoed back. Only on the two single-sensor endpoints

A partial success is still 202: five readings in, three accepted and two rejected, gives you 202 with the breakdown. Read the numbers — do not treat the status code alone as "everything landed".

"Accepted" means queued, not stored

An accepted reading is handed to the rule chain that applies to that sensor, and the chain decides what happens to it — including whether it is saved. A chain with no save step produces exactly this: clean 202 responses and no data anywhere in the interface. Every account starts with a Default Rule Chain that saves; if you built your own, check it under Management → Rule Chains.

Rejection reasons

The keys that can appear inside rejected:

KeyWhat it meansHow to fix it
dropped_unknown_sensorThe identifier matched no sensor on this deviceCompare it with the slug and UUID on the sensor's page. Matching is exact and case-sensitive
dropped_missing_identifierA batch entry had neither sensor_slug nor sensor_uuidGive every entry one of the two
dropped_missing_valueA reading had no valueSend value on every reading; do not send null for a failed sample, skip it
dropped_invalid_shapeThe data structure did not match the endpointSend an object to the single endpoint, an array to bulk and batch
dropped_future_timestampA timestamp was still ahead of server time after clock correctionSend an ISO 8601 timestamp, or omit it and let arrival time be used
dropped_duplicateThe same value for the same sensor arrived again within the suppression windowNothing to fix. Reported in duplicates, never inside rejected

Errors

StatusBodyCause and fix
401{"message":"Device authentication token required.","error":"missing_token"}No Authorization header, or not in Bearer … form
401{"message":"Invalid device authentication token.","error":"invalid_token"}The token does not belong to the device in the URL, or it was regenerated
404{"message":"Device not found.","error":"device_not_found"}The {device_uuid} in the URL is not a device. Copy it again from Device Details
404{"message":"No reading matched a sensor on this device.","error":"sensor_not_found","sensors":["temp_01"]}Nothing in the payload addressed a real sensor. sensors lists the identifiers that matched nothing
422{"message":"Validation failed.","errors":{"data.value":["The sensor value is required."]}}The request body is malformed. errors names the offending field
422{"message":"No readings were accepted.","error":"no_readings_accepted","rejected":{"dropped_missing_value":2}}The body was valid but every reading was dropped. rejected says why
429{"message":"Too Many Attempts."}Rate limit reached. Back off and retry — see below

Common validation failures behind 422 Validation failed: data missing or empty, value missing or not a number, timestamp not a parseable date, a batch entry with neither sensor_slug nor sensor_uuid, battery_voltage outside 0–100.

Rate limiting

The limit is 1000 requests per minute. It is counted per source address, so several devices behind one internet connection share the budget.

HeaderOnMeaning
X-RateLimit-LimitEvery responseThe ceiling — 1000
X-RateLimit-RemainingEvery responseRequests left in the current window
Retry-After429 onlySeconds to wait before retrying
X-RateLimit-Reset429 onlyWhen the window resets

Honour Retry-After rather than retrying immediately, and back off exponentially if you keep hitting it. If a device is anywhere near the limit, batch its sensors into one request instead of sending one request per sensor.

Worked example

curl -X POST \
https://api.sensocan.com/api/v1/devices/a1b2c3d4-e5f6-7890-abcd-ef1234567890/readings \
-H "Authorization: Bearer your-device-access-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"data": [
{ "sensor_slug": "temp_01", "value": 25.5 },
{ "sensor_slug": "humidity_01", "value": 60.2 }
],
"battery_voltage": 3.7
}'
{
"message": "Readings accepted and queued for rule chain processing.",
"accepted": 2,
"duplicates": 0,
"rejected": {}
}

Handling responses in firmware

  • 202 — clear your buffer for everything counted in accepted and duplicates. A reading in rejected will not succeed on a retry, so log it rather than resending it forever.
  • 401 and 404 — configuration, not transience. Stop retrying and raise it where an operator will see it.
  • 422 — a firmware problem. Log the body; it names the field at fault.
  • 429 — wait Retry-After seconds, then retry.
  • 5xx or no answer — transient. Back off exponentially and keep the readings buffered; timestamps make backfill exact, and a fast clock costs you nothing.

Troubleshooting

202 responses but no data anywhere. Open Device Details. The Sensors table shows each sensor's Current Value and Last updated.

The Sensors table on Device Details showing at least one sensor with a Current Value and a Last updated timestamp, and one sensor still reading 'No data', so the difference is visible side by side.
The Sensors table on Device Details showing at least one sensor with a Current Value and a Last updated timestamp, and one sensor still reading 'No data', so the difference is visible side by side.

If Last connected is updating but every sensor reads "No data", the rule chain is not saving them — check Management → Rule Chains. If some sensors update and others do not, the identifiers for the silent ones do not match a sensor on this device, and the 404 body and the rejected counts have been saying so.

Everything returns 401. Confirm the token was not regenerated, and that the Device UUID in the URL is the device this token belongs to. A token that works for one device is rejected on another.

Buffered readings come back as duplicates. That is the expected answer to a replay. Treat them as delivered and move your buffer on.