API
API-Referenz
Alle Endpunkte, Felder und Antworten der API für Geräte, direkt aus dem Vertrag (OpenAPI 3.1).
- Basis-URL
- https://boilerbuddy.eu/api/v1
- Vertrag
- BoilerBuddy device API 1.0.0
Die Referenz stammt aus dem Vertrag und ist auf Englisch.
Einleitung
The API a BoilerBuddy device — our own BoilerBuddy Alpo, or something you build yourself — uses to fetch its configuration and upload readings.
Ownership. This file is the contract: the server implements it, and the device software follows it. A change to the API is made here first.
Time. All timestamps are ISO-8601 with a timezone. The server stores UTC.
Idempotency. A reading is identified by device, sensor and measured_at (to the
second). Uploading the same reading twice is harmless: the first one wins and the
second counts as a duplicate. The Idempotency-Key header is accepted but the server
does not depend on it.
Partial acceptance. A well-formed batch always gets 200, also when some readings
are rejected, duplicates or for unknown sensors. The device may then delete the batch
from its buffer, except the rejected readings marked retry: true: it keeps those and
sends them again in a later batch. 422 means the batch as a whole is malformed and resending it
unchanged will not help.
Limits. Per token: 60 requests per minute in total, of which at most 10 uploads
to /device/readings. Per IP address: 600 requests per minute. A 429 carries
Retry-After; wait that long and resend the same batch. Catching up on a buffer
therefore goes at up to 10 000 readings per minute. Within a batch, readings may
fall on at most 7 calendar days (UTC): send the buffer in order, oldest first, and
a batch stays within a day or two. A device may quarantine at most 25 000 readings
for unknown sensors per day.
Authentifizierung
Authorization: Bearer …
bb_live_<device id>_<secret> for a live device, bb_test_… for a simulated one.
Shown once when the device is created or the token replaced.
GET/device/config
What to read, and how
Mit Token (Authorization: Bearer)
Sensors of the device's circuit with where each lives on the controller, the
controller's protocol and connection settings, and the scan interval. Poll with
If-None-Match to get 304 when nothing changed.
Parameter
| Feld | Wo | Beschreibung |
|---|---|---|
If-None-Matchstring | header |
Antworten
- 200
- 304
Unchanged since the given version
- 401
Missing, unknown, revoked or replaced token, or an inactive device
Schema: Error
- 429
More than 60 requests per minute for this token, more than 10 uploads of readings per minute, or more than 600 requests per minute from this address. Respect Retry-After.
Header:
Retry-After
POST/device/readings
Upload a batch of readings
Mit Token (Authorization: Bearer)
Parameter
| Feld | Wo | Beschreibung |
|---|---|---|
Idempotency-Keystring (uuid) | header | Optional; the batch_uuid. Correctness does not depend on it. |
Body der Anfrage
Schema: ReadingsBatch
{
"batch_uuid": "0d4f6c1e-8a51-4c43-9b6b-3c1f0f7a2e11",
"readings": [
{
"sensor": "T1",
"value": 61.4,
"measured_at": "2026-10-08T11:59:00Z"
},
{
"sensor": "R2",
"value": 1,
"measured_at": "2026-10-08T11:59:00Z"
},
{
"sensor": "T5",
"value": null,
"quality": "no_response",
"measured_at": "2026-10-08T11:59:00Z"
}
]
}Antworten
- 200
Batch processed. Delete it from the buffer.
Schema: ReadingsResult
- 401
Missing, unknown, revoked or replaced token, or an inactive device
Schema: Error
- 422
The request as a whole is invalid; do not resend it unchanged
Schema: ValidationError
- 429
More than 60 requests per minute for this token, more than 10 uploads of readings per minute, or more than 600 requests per minute from this address. Respect Retry-After.
Header:
Retry-After - 503
Temporarily unavailable; retry with backoff
POST/device/status
Heartbeat
Mit Token (Authorization: Bearer)
Send periodically, also when there is nothing to upload, so the server can tell "offline" from "quiet". Every authenticated request updates last-seen; this one also stores the device's own status.
Body der Anfrage
Schema: DeviceStatus
Antworten
- 204
Stored
- 401
Missing, unknown, revoked or replaced token, or an inactive device
Schema: Error
- 422
The request as a whole is invalid; do not resend it unchanged
Schema: ValidationError
- 429
More than 60 requests per minute for this token, more than 10 uploads of readings per minute, or more than 600 requests per minute from this address. Respect Retry-After.
Header:
Retry-After
POST/device/hello
Pair a new device
Ohne Token
For a device without a token. It generates a uuid once, keeps it, and sends it
here. Until a user claims the device, the answer is a pairing code to show
(BB-XXXX-XXXX, valid 24 hours; poll every 15 seconds or so, the same code comes
back until it expires, then a new one). The user enters the code in the portal.
The next hello returns the token. Store it before anything else, then use it
(/device/status or /device/config). Until the device has authenticated with
it, every hello returns a new token and the previous one stops working, so
a lost answer only means asking again. Once the device has used its token,
claimed is true without a token.
If the owner makes a new key by hand in the portal, the pairing is closed: the next hello gets a new pairing code, and nothing revokes the key made by hand.
Keep the uuid secret until the device has used its token: whoever knows it collects one. Limited to 10 requests per minute per uuid, and 30 per minute per IP address (an IPv6 /64 counts as one address), so a few boxes behind one router can pair at the same time.
Body der Anfrage
Schema: Hello
{
"device_uuid": "7b0e2f4c-1d3a-4e5b-9c6d-8f7a6b5c4d3e",
"hw_serial": "00000000abcd1234",
"firmware_version": "0.3.0"
}Antworten
- 200
Pairing state
Schema: HelloResult
waiting
{ "claimed": false, "pairing_code": "BB-7KQ4-M2XD", "expires_at": "2026-10-10T12:00:00+00:00" }Claimed; the token is not used yet (repeated with a new token until it is)
{ "claimed": true, "token": "bb_live_12_aBcD…" }Claimed, and the device has used its token
{ "claimed": true } - 422
The request as a whole is invalid; do not resend it unchanged
Schema: ValidationError
- 429
More than 10 requests per minute for this uuid, or 30 from this address. Respect Retry-After.
Header:
Retry-After
Schemas
ReadingsBatch
| Feld | Beschreibung |
|---|---|
batch_uuidstring (uuid) | Stable per batch, also when resent after a crash. |
clock | |
readingsPflicht | minItems: 1 · maxItems: 1000 |
Reading
| Feld | Beschreibung |
|---|---|
sensorPflichtstring | Beispiel: pattern: ^[A-Za-z0-9_.\-]{1,16}$ |
valuenumber | null | Required when |
qualitystring | Only Mögliche Werte: Standard: |
measured_atPflichtstring (date-time) | When the value was read. At most 5 minutes in the future and 365 days in the past. Stored to the second. |
clock_syncedboolean |
Standard: |
boot_idstring | Required for clock correction. |
mono_nsinteger | Monotonic clock at measurement. |
ClockAnchor
The device clock at the moment of sending. Optional; only needed for clock correction.
| Feld | Beschreibung |
|---|---|
boot_idPflichtstring | maxLength: 64 |
syncedPflichtboolean | |
sent_atPflichtstring (date-time) | |
sent_mono_nsPflichtinteger | minimum: 0 |
ReadingsResult
| Feld | Beschreibung |
|---|---|
acceptedPflichtinteger | Newly stored readings |
duplicatesPflichtinteger | Readings the server already had |
unmappedPflichtstring[] | Sensor codes that are not part of the device's circuit. These readings are kept in quarantine for 90 days, so a wiring or configuration mistake can be found; they are not counted as accepted. At most 16 unknown codes per batch. |
rejectedPflichtobject[] | |
rejected[].indexPflichtinteger | Position in |
rejected[].retryPflichtboolean | Keep this reading and send it again in a later batch. Only for
|
rejected[].reasonPflichtstring | Mögliche Werte: |
DeviceConfig
| Feld | Beschreibung |
|---|---|
config_versionPflichtstring | Hash of the content; equals the ETag. |
devicePflichtobject | |
device.idinteger | |
device.namestring | |
device.modestring | Mögliche Werte: |
intervalsPflichtobject | |
intervals.scan_secondsinteger | Beispiel: |
controllerPflichtobject | null | null while the device has no circuit |
controller.brandPflichtstring | Beispiel: |
controller.modelPflichtstring | Beispiel: |
controller.protocolPflichtstring | How the controller is read; now Beispiel: |
controller.connectionPflicht | Settings in the shape of the protocol. |
sensorsPflicht |
SensorConfig
| Feld | Beschreibung |
|---|---|
sensorstring | Beispiel: |
namestring | Beispiel: |
typestring | null | Beispiel: |
categorystring | null | Mögliche Werte: |
unitstring | null | Beispiel: |
positioninteger | null | Which tank or panel |
address | Where the controller keeps the value, in the shape of the controller's protocol; null when it does not have this sensor. |
plausible_minnumber | null | |
plausible_maxnumber | null |
ModbusConnection
For modbus_ascii and modbus_rtu
| Feld | Beschreibung |
|---|---|
slave_addressinteger | Beispiel: |
baudrateinteger | Beispiel: |
paritystring | Mögliche Werte: |
bytesizeinteger | Beispiel: |
stopbitsinteger | Beispiel: |
timeout_msinteger | Beispiel: |
ModbusAddress
For modbus_ascii and modbus_rtu. One 16-bit register, divided by 10 to the power of decimals.
| Feld | Beschreibung |
|---|---|
registerinteger | Beispiel: |
decimalsinteger | Beispiel: |
signedboolean | |
function_codeinteger | Mögliche Werte: |
DeviceStatus
| Feld | Beschreibung |
|---|---|
firmware_versionstring | maxLength: 32 |
uptime_sinteger | minimum: 0 |
ipstring | |
rssiinteger | minimum: -120 · maximum: 0 |
bufferedinteger | Readings waiting in the local buffer minimum: 0 |
last_errorstring | maxLength: 500 |
Hello
| Feld | Beschreibung |
|---|---|
device_uuidPflichtstring (uuid) | Generated once by the device and kept; random (version 4). |
hw_serialstring | For example the Pi's CPU serial maxLength: 64 |
firmware_versionstring | maxLength: 32 |
unit_keystring | Only a BoilerBuddy Alpo has one, from production. Claimed by the code on its sticker, it then gets its token without showing a code. Your own hardware leaves it out. maxLength: 64 |
HelloResult
| Feld | Beschreibung |
|---|---|
claimedPflichtboolean | |
pairing_codestring | Only while unclaimed. Show it to the user. pattern: ^BB-[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}$ |
expires_atstring (date-time) | Only while unclaimed |
tokenstring | After the claim, until the device has authenticated with a token: each answer carries a new one and the previous stops working. Never sent once one was used. |
Error
| Feld | Beschreibung |
|---|---|
messagestring |
ValidationError
| Feld | Beschreibung |
|---|---|
messagestring | |
errorsobject |