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:

76543210
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.

ParameterRequiredDescription
keyYesYour write key.
deviceYesDevice id, 0-15 (see device id above).
dataYesA 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 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:

MessageCause
POST requiredRequest wasn't sent as POST.
Key, device, and data arguments requiredMissing key, device, or data parameter.
Key not foundInvalid key.
Invalid device argumentdevice 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
ParameterRequiredDescription
keyYesYour write key.
deviceYesDevice id, 0-15 (see device id above).
nameNoDevice name.
locationNoWhere this device is, e.g. basement -- useful for telling apart multiple devices at the same write key.
channelsYesA |-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:

MessageCause
POST requiredRequest wasn't sent as POST.
Key, device, and channels arguments requiredMissing key, device, or channels parameter.
Key not foundInvalid key.
Invalid device argumentdevice outside 0-15.