> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bota.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Firmware Download URL

> A short-lived URL for one of a release’s firmware images

Returns a presigned URL for one of the two images in a release, along with the
checksum and size to verify it against.

**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` | Bota's own tooling. Bluetooth firmware transfer is not a supported integration path; see [Firmware Updates](/guides/firmware-updates). |

## Authentication

Requires an [API key](/authentication) with `devices:read` scope.

## Path Parameters

<ParamField path="id" type="string" required>
  The release identifier (e.g., `fw_3kQ9mZa7Rp2XvB4nT6yL8cW1`).
</ParamField>

## Query Parameters

<ParamField query="type" type="string" default="bin">
  Which image to sign — `bin` or `ufw`. Use `bin`.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.bota.dev/v1/firmware-releases/fw_3kQ9mZa7Rp2XvB4nT6yL8cW1/download-url" \
    -H "Authorization: Bearer sk_live_..."
  ```

  ```javascript Node.js theme={null}
  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');
  }
  ```

  ```python Python theme={null}
  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')
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "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"
  }
  ```

  ```json 404 theme={null}
  {
    "error": {
      "code": "not_found",
      "message": "FirmwareRelease not found"
    }
  }
  ```
</ResponseExample>

## 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](/api-reference/firmware/assign-ota).
* Request a fresh URL rather than storing one. The lifetime is short by design,
  and `expires_at` tells you exactly when it lapses.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.