curl -X POST https://api.bota.dev/v1/devices/dev_abc123/ota \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1"
}'
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/ota', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ firmware_release_id: 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1' }),
});
if (response.status === 409) {
// Something is already in flight for this device.
}
const assignment = await response.json();
import requests
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/ota',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={'firmware_release_id': 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1'},
)
if response.status_code == 409:
... # Something is already in flight for this device.
assignment = response.json()
{
"id": "ota_7Rp2XvB4nT6yL8cW1kQ9mZa3",
"device_id": "dev_abc123",
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1",
"status": "pending",
"assigned_at": "2026-08-14T12:00:00Z",
"delivered_at": null,
"applied_at": null,
"error_message": null
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
{
"error": {
"code": "resource_conflict",
"message": "Device already has a pending OTA assignment"
}
}
Firmware
Assign Firmware to Device
Queue a published firmware release for one device
POST
/
devices
/
{id}
/
ota
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/ota \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1"
}'
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/ota', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ firmware_release_id: 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1' }),
});
if (response.status === 409) {
// Something is already in flight for this device.
}
const assignment = await response.json();
import requests
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/ota',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={'firmware_release_id': 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1'},
)
if response.status_code == 409:
... # Something is already in flight for this device.
assignment = response.json()
{
"id": "ota_7Rp2XvB4nT6yL8cW1kQ9mZa3",
"device_id": "dev_abc123",
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1",
"status": "pending",
"assigned_at": "2026-08-14T12:00:00Z",
"delivered_at": null,
"applied_at": null,
"error_message": null
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
{
"error": {
"code": "resource_conflict",
"message": "Device already has a pending OTA assignment"
}
}
Queues a release for a single device. The device picks the update up on its next
heartbeat and downloads it itself, over WiFi or cellular. No further call is
needed — track it with Get Device OTA Status.
Allow up to a couple of minutes between assigning and delivery; the device only
learns about it when it next checks in.
A device holds one assignment at a time. If one is already in flight this
returns
409 — cancel it first with
Cancel Device OTA Assignment.Authentication
Requires an API key withdevices:write scope.
Path Parameters
string
required
The device’s unique identifier (e.g.,
dev_abc123).Request Body
string
required
The release to install. Must be published and belong to your organization —
an unpublished release answers
404.curl -X POST https://api.bota.dev/v1/devices/dev_abc123/ota \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1"
}'
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/ota', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ firmware_release_id: 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1' }),
});
if (response.status === 409) {
// Something is already in flight for this device.
}
const assignment = await response.json();
import requests
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/ota',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={'firmware_release_id': 'fw_3kQ9mZa7Rp2XvB4nT6yL8cW1'},
)
if response.status_code == 409:
... # Something is already in flight for this device.
assignment = response.json()
{
"id": "ota_7Rp2XvB4nT6yL8cW1kQ9mZa3",
"device_id": "dev_abc123",
"firmware_release_id": "fw_3kQ9mZa7Rp2XvB4nT6yL8cW1",
"status": "pending",
"assigned_at": "2026-08-14T12:00:00Z",
"delivered_at": null,
"applied_at": null,
"error_message": null
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
{
"error": {
"code": "resource_conflict",
"message": "Device already has a pending OTA assignment"
}
}
Response Fields
See Get Device OTA Status for the assignment shape and the meaning of eachstatus.
Notes
- A device connected to the mobile app over Bluetooth pauses its own heartbeat, so it will not receive an assignment while that connection is open.
- Manual assignment takes precedence over any automatic update the device would otherwise be offered.
Was this page helpful?

