Want to log your own sensor or device data here? All you need is a write key and anything that can send an HTTP POST request.
Two things recur throughout this API: a write key that authenticates every request, and a channel number that addresses every reading.
A write key authenticates every request and is tied to your location record (see Get a Write Key below). It's always exactly 16 characters: a letter (A-Z/a-z), followed by 15 more characters from A-Z, a-z, 0-9, -, or _ -- e.g. Kx7bQ2m-Z9p_LhWa. If you're validating or pre-filling it in your own device's setup (as the ESP32 sensor-node library does), match this shape:
^[A-Za-z][A-Za-z0-9_-]{15}$
Every reading is logged against a single channel number, 0-255. Channels are grouped into 16 devices of 16 channels each, so a channel number splits into a device id (0-15) and a per-device channel id (0-15):
channel = 16 × device_id + channel_id channel = device_id << 4 | channel_id
It's really just one byte, with the upper nibble as the device id and the lower nibble as the channel id:
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| device id | channel id | ||||||
Email larsi.org@gmail.com with a short description of your station (a name or location works well) and I'll set up a location record for you and send back a write key (see Write Key above) and your station's prefix.
Send a POST request to:
https://larsi.org/sensors/log
Use https:// directly rather than http:// -- an initial plain HTTP request would expose your write key before the redirect to HTTPS ever happens.
Only POST is accepted -- GET is not supported, so the write key never ends up in the web server's access log.
| Parameter | Required | Description |
|---|---|---|
key | Yes | Your write key. |
device | Yes | Device id, 0-15 (see device id above). |
data | Yes | A channel value list (see below). |
Any client that can POST a standard form-encoded body works. From the command line:
curl https://larsi.org/sensors/log \ -d "key=YOUR_KEY" \ -d "device=0" \ -d "data=72.5,45.2,1013.2"
curl -d sends a POST with a standard application/x-www-form-urlencoded body by default -- no extra headers needed.
data Parameterdata takes a comma-separated list of values, where each value's position in the list is its channel id within device -- there's no implicit default device, so it's always explicit which device a reading belongs to:
key=YOUR_KEY&device=0&data=72.5,45.2,1013.2
Logs channel 0 = 72.5, channel 1 = 45.2, channel 2 = 1013.2, all on device 0. Leave a value blank to skip that channel entirely -- it won't be logged at all, not logged as zero:
key=YOUR_KEY&device=0&data=72.5,,1013.2
Logs channel 0 = 72.5 and channel 2 = 1013.2; channel 1 is skipped.
To log a second device's channels without colliding with the first device's numbering (see the bit layout above), use its device id instead:
key=YOUR_KEY&device=1&data=72.5,45.2
Logs channel 16 = 72.5, channel 17 = 45.2.
The response is plain text. Every request starts with a line confirming the timestamp used, followed by Data logged once at least one channel was actually inserted:
Logging... 2026-08-13 20:15:32 (1786652132) Data logged
On failure, nothing is logged and one of these appears instead:
| Message | Cause |
|---|---|
POST required | Request wasn't sent as POST. |
Key, device, and data arguments required | Missing key, device, or data parameter. |
Key not found | Invalid key. |
Invalid device argument | device isn't a plain non-negative number. |
Provisioning registers a device and its channels ahead of time, so reports show real labels (a device name, location, and each channel's sensor/property/unit) instead of raw numbers. It's optional -- log works fine with no provisioning at all -- and safe to call every boot: it's a non-empty upsert, meaning a field with a real value updates the row (so a genuine rename or a swapped sensor takes effect), but leaving a field blank never overwrites a value you set by hand. A channel's description (the label shown in reports) is the one exception -- it's only ever set from property the first time a channel is seen, so a hand-edited description always survives.
Send a POST request to:
https://larsi.org/sensors/provision
| Parameter | Required | Description |
|---|---|---|
key | Yes | Your write key. |
device | Yes | Device id, 0-15 (see device id above). |
name | No | Device name. |
location | No | Where this device is, e.g. basement -- useful for telling apart multiple devices at the same write key. |
channels | Yes | A |-separated list of channel_id,sensor,property,unit quadruples. |
curl https://larsi.org/sensors/provision \ -d "key=YOUR_KEY" \ -d "device=0" \ -d "name=My Weather Board" \ -d "location=basement" \ -d "channels=0,BME280,Temperature,C|1,BME280,Humidity,%"
Each entry in channels is channel_id,sensor,property,unit -- e.g. 0,BME280,Temperature,C registers channel 0 as a BME280 sensor reporting Temperature in C. sensor is the physical hardware's model name (e.g. BME280, DS18B20, SHT15). channel_id must be 0-15 (see Data Channel above).
The response is plain text, confirming what was created or updated:
Provisioning... Provisioned Device mydevice[0]: created, 2 new channel(s) added, 0 channel(s) updated
The device status is created, updated, or unchanged, depending on whether this call's name/location actually changed anything.
On failure, nothing is created and one of these appears instead:
| Message | Cause |
|---|---|
POST required | Request wasn't sent as POST. |
Key, device, and channels arguments required | Missing key, device, or channels parameter. |
Key not found | Invalid key. |
Invalid device argument | device outside 0-15. |