Zum Inhalt
BoilerBuddy

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
OpenAPI-Spezifikation

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

FeldWoBeschreibung
If-None-Match
string
header

Antworten

  • 200

    Current configuration

    Schema: DeviceConfig

    Header: ETag

  • 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

FeldWoBeschreibung
Idempotency-Key
string (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

FeldBeschreibung
batch_uuid
string (uuid)

Stable per batch, also when resent after a crash.

clock
readingsPflicht

minItems: 1 · maxItems: 1000

Reading

FeldBeschreibung
sensorPflicht
string

Beispiel: T1

pattern: ^[A-Za-z0-9_.\-]{1,16}$

value
number | null

Required when quality is ok; may be null or left out otherwise. Numeric strings are accepted for compatibility. Absolute values above 1e9 are rejected.

quality
string

Only ok readings count towards averages, minima and maxima.

Mögliche Werte: oksensor_faultout_of_rangeno_response

Standard: ok

measured_atPflicht
string (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_synced
boolean

false if the wall clock was not yet synchronised when this reading was taken. The server then corrects measured_at from mono_ns and the batch's clock anchor, provided boot_id matches; otherwise the reading is rejected with clock_unsynced. Without a synchronised anchor that rejection carries retry: true: send the reading again once the clock is synchronised.

Standard: true

boot_id
string

Required for clock correction.

mono_ns
integer

Monotonic clock at measurement.

ClockAnchor

The device clock at the moment of sending. Optional; only needed for clock correction.

FeldBeschreibung
boot_idPflicht
string

maxLength: 64

syncedPflicht
boolean
sent_atPflicht
string (date-time)
sent_mono_nsPflicht
integer

minimum: 0

ReadingsResult

FeldBeschreibung
acceptedPflicht
integer

Newly stored readings

duplicatesPflicht
integer

Readings the server already had

unmappedPflicht
string[]

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.

rejectedPflicht
object[]
rejected[].indexPflicht
integer

Position in readings

rejected[].retryPflicht
boolean

Keep this reading and send it again in a later batch. Only for clock_unsynced without a synchronised anchor, and batch_spans_too_many_days.

rejected[].reasonPflicht
string

Mögliche Werte: invalid_iteminvalid_sensorinvalid_valuevalue_requiredinvalid_qualityinvalid_measured_atmeasured_at_in_futuremeasured_at_too_oldclock_unsyncedtoo_many_unknown_sensorsunmapped_quota_exceededbatch_spans_too_many_days

DeviceConfig

FeldBeschreibung
config_versionPflicht
string

Hash of the content; equals the ETag.

devicePflicht
object
device.id
integer
device.name
string
device.mode
string

Mögliche Werte: livesimulated

intervalsPflicht
object
intervals.scan_seconds
integer

Beispiel: 60

controllerPflicht
object | null

null while the device has no circuit

controller.brandPflicht
string

Beispiel: OEG

controller.modelPflicht
string

Beispiel: KSW-E, KSW, KS2W

controller.protocolPflicht
string

How the controller is read; now modbus_ascii or modbus_rtu. More protocols may follow, so take any string and skip a controller whose protocol you do not know.

Beispiel: modbus_ascii

controller.connectionPflicht

Settings in the shape of the protocol.

sensorsPflicht

SensorConfig

FeldBeschreibung
sensor
string

Beispiel: T1

name
string

Beispiel: Temperatuur zonnepaneel

type
string | null

Beispiel: collector_temp

category
string | null

Mögliche Werte: temperaturerelayenergyflownull

unit
string | null

Beispiel: °C

position
integer | 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_min
number | null
plausible_max
number | null

ModbusConnection

For modbus_ascii and modbus_rtu

FeldBeschreibung
slave_address
integer

Beispiel: 128

baudrate
integer

Beispiel: 9600

parity
string

Mögliche Werte: NEO

bytesize
integer

Beispiel: 7

stopbits
integer

Beispiel: 1

timeout_ms
integer

Beispiel: 1000

ModbusAddress

For modbus_ascii and modbus_rtu. One 16-bit register, divided by 10 to the power of decimals.

FeldBeschreibung
register
integer

Beispiel: 38

decimals
integer

Beispiel: 1

signed
boolean
function_code
integer

Mögliche Werte: 34

DeviceStatus

FeldBeschreibung
firmware_version
string

maxLength: 32

uptime_s
integer

minimum: 0

ip
string
rssi
integer

minimum: -120 · maximum: 0

buffered
integer

Readings waiting in the local buffer

minimum: 0

last_error
string

maxLength: 500

Hello

FeldBeschreibung
device_uuidPflicht
string (uuid)

Generated once by the device and kept; random (version 4).

hw_serial
string

For example the Pi's CPU serial

maxLength: 64

firmware_version
string

maxLength: 32

unit_key
string

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

FeldBeschreibung
claimedPflicht
boolean
pairing_code
string

Only while unclaimed. Show it to the user.

pattern: ^BB-[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}$

expires_at
string (date-time)

Only while unclaimed

token
string

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

FeldBeschreibung
message
string

ValidationError

FeldBeschreibung
message
string
errors
object