Skip to content

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 200 for business outcomes (no update available, plan over limit, device deactivated) with a status field. Real 4xx/5xx errors 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

{ "accepted": true, "final_status": "confirmed" }

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.