Device API¶
The endpoints ESP32 devices call. Both accept either a project token
(with device_id in the request body) or a per-device token (issued
from the dashboard's device detail page).
Hardware-safe responses. Both endpoints return
200for business outcomes (no update available, plan over limit, device deactivated) with astatusfield. Real4xx/5xxerrors are reserved for genuine misuse or infrastructure failure, so a fleet won't retry-storm you on routine outcomes.
POST /api/v1/ota/check/¶
Asks SimpleOTA "is there an update for me?" and reports a heartbeat.
Request¶
{
"device_id": "esp32-demo-0001",
"framework": "arduino",
"chip_family": "esp32",
"board_id": "esp32dev",
"current_build_number": 17,
"version_label": "1.4.1", // display label only — not used for compatibility
"current_hash": "8f3c…",
"hardware_revision": "rev-a",
"channel": "stable",
"partition_profile": "default_4mb",
"nvs_schema_version": 2,
"security_mode": "basic"
}
| Field | Required | Notes |
|---|---|---|
device_id |
✅ | Stable per unit. Required in project-token mode. |
framework |
✅ | arduino or esp_idf. |
chip_family |
✅ | esp32, esp32s3, esp32c3, etc. |
current_build_number |
✅ | The build_number from the last /check/ offer that the device successfully installed, persisted in NVS. Send 0 on a factory-fresh device. Never use your own internal build counter here. See Build numbers. |
board_id |
Required if your artifacts declare it. | |
hardware_revision |
Required if your artifacts declare it. | |
version_label |
Display label for the firmware currently running (e.g. "1.4.1"). Stored on the device record and shown in the dashboard under Reported firmware state → Version label. Not used in compatibility checks or update eligibility — it is purely for human visibility. Read from NVS after installation. |
|
current_hash |
SHA-256 hex digest of the currently-running binary. Stored on the device record and shown in the dashboard under Reported firmware state → Binary SHA-256. Read from NVS after installation. | |
channel |
Release channel to assign this device to (e.g. stable, beta). Only applied on first registration or if the device has no channel yet. |
|
partition_profile |
Partition table layout the device is using (e.g. default_4mb, min_spiffs). Enforced when the artifact declares a profile: a mismatch blocks the update. Omit or leave blank if your firmware does not use partition-scoped images; blank always matches. |
|
nvs_schema_version |
Integer NVS schema version currently written to flash. Enforced when the artifact declares a minimum version: if device < artifact, the update is blocked to prevent NVS data corruption. Device may omit this field; omitting always matches. |
|
security_mode |
Security handshake mode the device expects (e.g. basic, token). Enforced when the artifact declares a mode: a mismatch blocks the update. Omit or leave blank if your firmware does not differentiate; blank always matches. |
Response: update available¶
{
"update_available": true,
"build_number": 23,
"version": "1.4.2",
"url": "https://<bucket>/firmware/<key>?X-Amz-Signature=…",
"checksum": "8f3c…",
"size": 1287456,
"deployment_id": "9a1c…",
"security_mode": "basic"
}
The device should download url, verify checksum (SHA-256), install,
then call /status/.
Response: no update¶
{
"update_available": false,
"status": "up_to_date",
"message": "Device is already on the latest compatible build."
}
status values:
status value |
Meaning |
|---|---|
up_to_date |
Device is at the highest assigned compatible build. |
no_active_deployment |
No active deployment targets this device. |
not_in_rollout |
Eligible, but cohort bucket is above the current percentage. |
no_compatible_build |
Deployments target the device's audience but no artifact is hardware-compatible. |
over_limit |
Plan device cap reached; auto-registration refused. |
device_inactive |
Device record exists but is active = false. |
HTTP status codes¶
| Code | When |
|---|---|
200 |
All business outcomes, including no-update. |
400 |
Malformed request body. |
401 |
Missing/invalid project token. |
403 |
Token belongs to a different project. |
5xx |
Genuine server failure. Devices may retry. |
POST /api/v1/ota/status/¶
Reports an OTA lifecycle event for the calling device.
Request¶
{
"device_id": "esp32-demo-0001",
"deployment_id": "9a1c…",
"event": "confirmed",
"build_number": 23,
"reason": null,
"detail": null
}
| Field | Required | Notes |
|---|---|---|
device_id |
✅ | Required in project-token mode so we know which device the event is for. |
deployment_id |
✅ | The deployment the event pertains to. |
event |
✅ | See event vocabulary below. |
build_number |
Required with confirmed so SimpleOTA can advance the device's recorded build. |
|
reason |
Short token explaining the outcome, especially for failed and rolled_back. |
|
detail |
Optional JSON object with extra structured detail. |
Event vocabulary (v0.2+)¶
Events in canonical order for a successful update:
| Event | When | Terminal? | Idempotent? |
|---|---|---|---|
offered |
Device acknowledged the offer | ||
download_started |
Firmware download began | ||
downloaded |
Binary fully received | ||
flashed |
Written to flash | ||
validated |
Image committed to NVS (informational) | ||
reboot |
Emitted immediately before esp_restart() |
||
confirmed |
Device booted new image and called confirmRunning() |
✅ yes | ✅ yes |
failed |
Any error before or during apply() |
✅ yes | |
rolled_back |
Trial-install timeout; device rolled back | ✅ yes | ✅ yes |
The legacy rebooted event (fired by v0.1 devices after booting the new image) is still accepted.
confirmed and rolled_back are persisted in NVS by the client and retried on every
successful /check/ until the server responds with "accepted": true. The server handles
both idempotently: repeat POSTs for the same (device_id, deployment_id) are safe no-ops.
Failure reasons¶
reason is a free-form short token (keep it under ~64 chars, lowercase,
snake_case). The dashboard displays it verbatim on the device row and in
the per-device timeline. Canonical tokens that clients should use when
they apply, so fleets stay comparable across firmware versions:
| Reason | Sent with | Meaning |
|---|---|---|
signature_invalid |
failed |
The downloaded image failed Ed25519 signature verification against the device's pinned public key (signed firmware). The device did not mark the image bootable. |
checksum_mismatch |
failed |
SHA-256 of the downloaded bytes did not match the offer's checksum. |
download_failed |
failed |
Network or storage error before the image was fully received. |
flash_write_failed |
failed |
The OTA partition write or finalize step failed. |
confirm_timeout |
rolled_back |
The trial image did not confirm within the timeout; bootloader rolled back. |
A burst of signature_invalid across a fleet is a security signal, not a
flakiness signal: it means devices are rejecting an image they were
offered. Pause the deployment and investigate the key and pipeline before
anything else.
Response¶
A confirmed event is the terminal success signal: the device has booted the new image,
called confirmRunning(), and is operating normally. SimpleOTA updates the device's recorded
current_build_number to the build_number in this payload.
failed or rolled_back events surface the reason string in the
dashboard and increment the deployment's failure counter.