curl "https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url" \
-H "Authorization: Bearer sk_live_..."
import { createHash } from 'node:crypto';
const meta = await fetch(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
{ headers: { 'Authorization': 'Bearer sk_live_...' } },
).then((r) => r.json());
const image = Buffer.from(await fetch(meta.download_url).then((r) => r.arrayBuffer()));
if (createHash('sha256').update(image).digest('hex') !== meta.sha256) {
throw new Error('Firmware image did not match its checksum');
}
import hashlib
import requests
meta = requests.get(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
headers={'Authorization': 'Bearer sk_live_...'},
).json()
image = requests.get(meta['download_url']).content
if hashlib.sha256(image).hexdigest() != meta['sha256']:
raise ValueError('Firmware image did not match its checksum')
{
"version": "1.0.18",
"download_url": "https://bota-firmware-prod.s3.us-west-2.amazonaws.com/...&X-Amz-Expires=3600",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"file_size_bytes": 1048576,
"expires_at": "2026-08-14T11:02:00Z"
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
Firmware
Get Firmware Download URL
A short-lived URL for one of a release’s firmware images
GET
/
firmware-releases
/
{id}
/
download-url
curl "https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url" \
-H "Authorization: Bearer sk_live_..."
import { createHash } from 'node:crypto';
const meta = await fetch(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
{ headers: { 'Authorization': 'Bearer sk_live_...' } },
).then((r) => r.json());
const image = Buffer.from(await fetch(meta.download_url).then((r) => r.arrayBuffer()));
if (createHash('sha256').update(image).digest('hex') !== meta.sha256) {
throw new Error('Firmware image did not match its checksum');
}
import hashlib
import requests
meta = requests.get(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
headers={'Authorization': 'Bearer sk_live_...'},
).json()
image = requests.get(meta['download_url']).content
if hashlib.sha256(image).hexdigest() != meta['sha256']:
raise ValueError('Firmware image did not match its checksum')
{
"version": "1.0.18",
"download_url": "https://bota-firmware-prod.s3.us-west-2.amazonaws.com/...&X-Amz-Expires=3600",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"file_size_bytes": 1048576,
"expires_at": "2026-08-14T11:02:00Z"
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
Platform release selection: service firmware must be explicitly released for your authenticated project in the platform, as well as published in OTA. Available but unselected, stopped, superseded or upstream-withdrawn versions are not eligible for new public requests. See Firmware Updates.
version. Pass version unchanged to the App SDK and fail before transfer if it is missing.
Verify what you download before installing it. Compare the SHA-256 of the
bytes you received against the sha256 in this response; a truncated or
corrupted image that reaches a device is not recoverable over the air.
Which image
type | File | Used by |
|---|---|---|
bin (default) | .bin | The device downloading over WiFi or cellular — the update path available to your integration |
ufw | .ufw | App SDK Bluetooth transfer with a device/release-bound OTA grant; see Firmware Updates. |
Authentication
Requires an API key withdevices:read scope.
Path Parameters
string
required
The release identifier (e.g.,
fw_3kQ9mZa7Rp2XvB4nT6yL8cW1).Query Parameters
string
default:"bin"
Which image to sign —
bin for direct network OTA or ufw for App SDK Bluetooth OTA.curl "https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url" \
-H "Authorization: Bearer sk_live_..."
import { createHash } from 'node:crypto';
const meta = await fetch(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
{ headers: { 'Authorization': 'Bearer sk_live_...' } },
).then((r) => r.json());
const image = Buffer.from(await fetch(meta.download_url).then((r) => r.arrayBuffer()));
if (createHash('sha256').update(image).digest('hex') !== meta.sha256) {
throw new Error('Firmware image did not match its checksum');
}
import hashlib
import requests
meta = requests.get(
'https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url',
headers={'Authorization': 'Bearer sk_live_...'},
).json()
image = requests.get(meta['download_url']).content
if hashlib.sha256(image).hexdigest() != meta['sha256']:
raise ValueError('Firmware image did not match its checksum')
{
"version": "1.0.18",
"download_url": "https://bota-firmware-prod.s3.us-west-2.amazonaws.com/...&X-Amz-Expires=3600",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"file_size_bytes": 1048576,
"expires_at": "2026-08-14T11:02:00Z"
}
{
"error": {
"code": "not_found",
"message": "FirmwareRelease not found"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
download_url | string | Presigned URL. Requires no authentication header of its own. |
sha256 | string | Checksum of the image this URL serves |
file_size_bytes | number | Size of the image in bytes |
expires_at | string | ISO 8601. The URL stops working at this time — do not cache it past it. |
Notes
- You do not need this endpoint for the WiFi and cellular update path. A device with an assignment receives its own download URL in the heartbeat response — see Assign Firmware to Device.
- Request a fresh URL rather than storing one. The lifetime is short by design,
and
expires_attells you exactly when it lapses.
Was this page helpful?

