Firmware
Upload firmware builds with their release notes, decide which devices are offered them,
and let devices ask for newer builds. Each build is identified by its firmware name,
hardware name and version, version and type (AFI, MFI or EFI). Most of these come
from the what string embedded in the binary: a NUL-terminated string starting with
@(#), such as
@(#)1.8.0, demo, example-hw@2.0.0, 2026-02-01T10:00:00Z, AFI
Shorter what strings are accepted too (1.4.1-10-g30d0049, 2025-07-14T22:25:07Z, AFI);
you then supply the missing fields in the upload URL or as form fields. See
Concepts for the formats and
version ordering.
Builds are write-once. A build's binary and release notes never change and builds are
never deleted. Uploading the identical binary and notes again is harmless (200); a
different binary or notes for the same identity is refused (409) — release a new version
instead. X.Y.Z and X.Y.Z-0-gHASH are the same version, so they are the same identity:
uploading 0.3.2 when 0.3.2-0-gfac16cc exists is a 409 naming the existing build. What you can change are its release settings: its tags, whether it is a
general release, and its status. Withdrawing a build (status: withdrawn) stops it being
offered and its download links stop working; its identity stays reserved, so the same
version can never be uploaded again (409), even with identical bytes.
A new build is staged: no device is offered it until you tag it for a group of devices or mark it as a general release. Devices find builds with the update check.
The firmware object
| Field | Type | Description |
|---|---|---|
id |
string | UUID |
fw_name, hw_name, hw_version |
string | product and hardware the build is for |
fw_version |
string | version exactly as in the what string, e.g. v1.5.1.2 |
version |
string | canonical version used for ordering and identity, e.g. 1.5.1.2 |
fw_type |
string | AFI (application), MFI (manufacturing) or EFI (engineering) |
build_date |
timestamp or null | from the what string |
what_string, what_format |
string | the what string used and its detected format |
fields_from_request |
array | fields that came from the request instead of the what string |
size_bytes, sha256 |
integer, string | the binary |
notes_size, notes_sha256, notes_filename |
the release notes | |
tag_filter_disabled |
boolean | true = general release: offered to every device |
tags |
array | devices sharing one of these tags are offered the build |
status |
string | active or withdrawn (never offered; download links stop working) |
uploaded_at, uploaded_by, updated_at |
audit fields | |
download_count, notes_download_count, last_accessed_at |
usage |
Check for updates
GET /firmware/{domain}/{hw_name}/{hw_version}/{fw_name}/{fw_version}[/{hwid}]
No authentication
Devices ask "is there anything newer than what I run?". No authentication is needed:
the account is named by its domain in the path. The answer lists newer builds, oldest
first, each with download links for the binary and the release notes that are valid for
5 minutes. Append the device's HW id as the optional last path segment to also get
builds targeted at the device's tags (percent-encode reserved characters, e.g. lab/7 →
lab%2F7). In the sandbox, a1b2c3d4 is tagged beta, so it also gets the beta build
1.7.0-rc.1; leave the HW id empty in Try it to see general releases only.
How builds are chosen
- Take the account's active firmware with the same firmware name, hardware name and
hardware version, and an allowed type (default
AFI). - Keep builds whose version is strictly later than the device's version (see
version ordering):
1.4.1-10-g30d0049is later than1.4.1, and1.5.1.2is later than1.5.1. - Keep a build if it is a general release, or if the device's HW id (the optional last path segment) belongs to a registered device that shares at least one tag with it. The result is the union of the two.
- Sort by version ascending, then build date, then type (AFI, MFI, EFI).
A HW id that isn't registered, or belongs to a withdrawn device, is treated exactly like no HW id, so the check can't be used to discover device ids.
Response format. The answer is always a JSON array. Errors are one line of plain text
by default, or application/problem+json if the request has Accept: application/json.
Responses carry Cache-Control: no-store, and cross-origin browser requests are allowed.
The update object — each array item:
| Key | Description |
|---|---|
id |
firmware id |
version |
{hw_name}/{hw_version}/{fw_name}/{version}, with the full canonical version |
fw-version |
the version exactly as in the what string (e.g. v1.5.1.2) |
fw-type |
AFI, MFI or EFI |
build-date |
RFC 3339 build date, or null |
size |
binary size in bytes |
sha256 |
hex SHA-256 of the binary — verify it after downloading |
firmware-url |
5-minute link to the binary |
release-notes-url |
5-minute link to the release notes |
expires |
when the two links stop working |
general-release |
true if offered to every device, false if offered through a tag |
Device checklist: send the full running version (git describe output, not just
X.Y.Z); append the HW id if the device should receive tag-targeted builds; download
promptly; verify sha256 before flashing; poll at most hourly or once per boot and honour
Retry-After on 429.
An unknown firmware or hardware name is not an error; it just returns [].
Path parameters
domainstring requiredYour account's domain.
hw_namestring requiredHardware name (case-insensitive).
hw_versionstring requiredHardware version (a leading
vis ignored).fw_namestring requiredFirmware name (case-insensitive).
fw_versionstring requiredThe version the device runs now, in full, e.g.
1.6.0-10-g30d0049. Avprefix and four-part versions are fine.hwidstringOptional: the device's HW id (1–64 printable characters, case-sensitive, percent-encoded). Adds builds that share one of the device's tags.
Query parameters
typestringAFI(default),all, or a list such asAFI,MFI.latestbooleantrue: only the newest matching build.
Headers
Acceptstringapplication/jsonforapplication/problem+jsonerrors; otherwise errors are plain text.
curl "https://fw.unitcircle.ca/firmware/sandbox.example/example-hw/2.0.0/demo/1.6.0-10-g30d0049/a1b2c3d4"
http GET "https://fw.unitcircle.ca/firmware/sandbox.example/example-hw/2.0.0/demo/1.6.0-10-g30d0049/a1b2c3d4"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware/sandbox.example/example-hw/2.0.0/demo/1.6.0-10-g30d0049/a1b2c3d4",
)
print(r.status_code, r.json())
[
{
"build-date": "2026-01-10T09:00:00Z",
"expires": "2026-09-26T01:06:49Z",
"firmware-url": "https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwEBoNs4tA5zEZyOsJKJRQe4AAAAAGq3GqlIDKRVL0AHiw.7eh3rKhfdaJTLCCKMhJuR3m9EjQ",
"fw-type": "AFI",
"fw-version": "1.7.0-rc.1",
"general-release": false,
"id": "01a0db38-b40e-7311-9c8e-b092894507b8",
"release-notes-url": "https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwIBoNs4tA5zEZyOsJKJRQe4AAAAAGq3GqkpcjZQolgmPw.Gg99hBIx9LPo1t2GheHclynE4b8",
"sha256": "053099e9651d6efc083f595967ee0ff684c23c4b6831f8f9481ba42e6f36d9a4",
"size": 4096,
"version": "example-hw/2.0.0/demo/1.7.0-rc.1"
}
][
{
"build-date": "2025-07-14T22:25:07Z",
"expires": "2026-09-26T01:06:49Z",
"firmware-url": "https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwEBoNs4s-B8IZDShw7BYgCaAAAAAGq3GqmNk1wTwsQS0w.tG9Hl7Ix5Hn2uDk6YBkjbPBAFaw",
"fw-type": "AFI",
"fw-version": "1.4.1-10-g30d0049",
"general-release": true,
"id": "01a0db38-b3e0-7c21-bd82-18282b601d5f",
"release-notes-url": "https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwIBoNs4s-B8IZDShw7BYgCaAAAAAGq3GqkA0XbgZ_fq3w.U7Qh7C1bWz2Kx0R9bm9ADV2V3jU",
"sha256": "600964614f3515690a9d0cbab6b22e2e1aff6193e51e8f004269732cfbf3ebb5",
"size": 4096,
"version": "example-hw/2.0.0/demo/1.4.1-10-g30d0049"
},
{
"build-date": "2025-12-01T22:11:31Z",
"expires": "2026-09-26T01:06:49Z",
"firmware-url": "https://fw.unitcircle.ca/dl/…",
"fw-type": "AFI",
"fw-version": "v1.5.1.2",
"general-release": true,
"id": "01a0db38-b3f1-73b5-85c9-648c2ed0233e",
"release-notes-url": "https://fw.unitcircle.ca/dl/…",
"sha256": "95365e1a8ad688c8cb8bf20dafb7124d2ea7c146e2c2de129c94eb30369aacce",
"size": 4096,
"version": "example-hw/2.0.0/demo/1.5.1.2"
},
{
"build-date": "2025-12-05T20:25:18Z",
"expires": "2026-09-26T01:06:49Z",
"firmware-url": "https://fw.unitcircle.ca/dl/…",
"fw-type": "AFI",
"fw-version": "1.6.0-10-g30d0049",
"general-release": true,
"id": "01a0db38-b3fb-7af4-b46b-638aec828af5",
"release-notes-url": "https://fw.unitcircle.ca/dl/…",
"sha256": "81e709dc4a199c37289ee00796b62be7b54aba55b32fa476bee637730037f18a",
"size": 4096,
"version": "example-hw/2.0.0/demo/1.6.0-10-g30d0049"
}
][]
404 Not Found: unknown customer domain
400 Bad Request: fw-version: invalid version: "1.x" (expected X.Y.Z or X.Y.Z.W, optional -pre-release and -N-gHASH)
400 Bad Request: type must be 'all' or a comma-separated list of AFI, EFI, MFI
429 Too Many Requests: rate limit exceeded; retry after 1 s
400 Bad Request: hwid must be 1–64 printable ASCII characters without spaces
Upload firmware
PUT /firmware[/{hw_name}/{hw_version}/{fw_name}]
Bearer token · scope fw:write
Upload a binary and its release notes as multipart/form-data. The three path segments
are optional: use PUT /firmware when the what string contains every field (version,
firmware name, hw-name@hw-version, date, type), or
PUT /firmware/{hw_name}/{hw_version}/{fw_name} for binaries whose what string lacks the
product fields (e.g. 1.9.0, 2026-03-01T10:00:00Z, AFI). Fields present in both must
match. Missing fields can also be given as form fields or query parameters.
The response format follows the Accept header: without it the answer is 200 with the
body OK and errors are one line of plain text; with Accept: application/json you get
the stored firmware object (201 for a new build, 200 for an identical re-upload) and
application/problem+json errors. Builds are write-once: the same identity with a
different binary or notes, or the identity of a withdrawn build, is a 409.
The example needs no files: its "binary" is just a what string, which is enough for the
service to read the version, names and type (a what string may end at a NUL byte or at the
end of the file). A real upload sends your build output, e.g. -F firmware=@build/demo.bin
with cURL. Running the example again returns 200 — the same build, unchanged. Try it
lets you edit the what string (bump the version to upload a new build) or fill in the three
optional path segments for a what string without product names, such as
@(#)1.9.4, 2026-09-26T12:00:00Z, AFI.
Other errors: several different what strings without what_index → 422; no what
string at all → 422; release notes not UTF-8 → 422; missing firmware or notes part
→ 400; parts too large → 413; maintenance mode → 503 with Retry-After.
Path parameters
hw_namestringOptional: hardware product name, for what strings without it.
hw_versionstringOptional: hardware version (a leading
vis ignored).fw_namestringOptional: firmware product name.
Query parameters
what_indexintegerWhich what string to use when the binary has several different ones (see Parse a binary).
Headers
Acceptstringapplication/jsonfor a JSON response; otherwise plain text.
Form parts (multipart/form-data)
firmwarefile requiredThe binary (max 16 MiB; 1 MiB in the sandbox). Must contain a what string.
notesfile requiredRelease notes, UTF-8 (max 1 MiB). A
.mdfile name marks them as Markdown.fw_name, hw_name, hw_version, fw_type, fw_version, build_datestringFields missing from the what string (also accepted as query parameters).
general_releasebooleantrue: publish immediately to every device.
curl -X PUT "https://fw.unitcircle.ca/firmware" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA" \ -H "Accept: application/json" \ --form-string 'firmware=@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI' \ --form-string 'notes=# demo 1.9.3 - Faster start-up. '
http --multipart -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA PUT "https://fw.unitcircle.ca/firmware" \ 'Accept:application/json' \ 'firmware=\@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI' \ 'notes=# demo 1.9.3 - Faster start-up. '
import requests
r = requests.put(
"https://fw.unitcircle.ca/firmware",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA", "Accept": "application/json"},
files={"firmware": ("demo-1.9.3.bin", "@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI"), "notes": ("README.md", "# demo 1.9.3\n\n- Faster start-up.\n")},
)
print(r.status_code, r.json())
{
"id": "01a0ded0-f69d-77e7-a321-002fef69a4cf",
"fw_name": "demo",
"hw_name": "example-hw",
"hw_version": "2.0.0",
"fw_version": "1.9.3",
"version": "1.9.3",
"fw_type": "AFI",
"build_date": "2026-09-26T12:00:00Z",
"what_string": "1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI",
"what_format": "v2-5field",
"fields_from_request": [],
"size_bytes": 60,
"sha256": "394cccafdb7042d1e58a52aa76a392adaf8a41d2a1463783c26e14504499fa50",
"notes_size": 33,
"notes_sha256": "45045e4f10dcc2e2399359afc4be5fbb8330444b3d31ac6f4bbbde7303c696b0",
"notes_filename": null,
"tag_filter_disabled": false,
"status": "active",
"uploaded_at": "2026-09-26T17:43:51.70613Z",
"uploaded_by": "token:01a0dac8-6da3-7697-a806-fac3085296d0",
"download_count": 0,
"notes_download_count": 0,
"last_accessed_at": null,
"updated_at": "2026-09-26T17:43:51.714744Z",
"tags": []
}OK
{"type":"about:blank","title":"Conflict","status":409,"detail":"firmware demo 1.8.0 for example-hw@2.0.0 (AFI) already exists with different content (firmware cannot be replaced; release a new version)","id":"01a0db3a-1883-73e4-b695-e6f21b5ade15","instance":"/firmware","request_id":"…"}{"type":"about:blank","title":"Conflict","status":409,"detail":"firmware demo 1.8.0 for example-hw@2.0.0 (AFI) was withdrawn; its version cannot be uploaded again","id":"01a0db3a-1883-73e4-b695-e6f21b5ade15","instance":"/firmware","request_id":"…"}409 Conflict: firmware demo 1.8.0 for example-hw@2.0.0 (AFI) already exists with different content (firmware cannot be replaced; release a new version)
{"type":"about:blank","title":"Unprocessable Entity","status":422,"detail":"missing fields not in the what string: fw_name, hw_name, hw_version (supply them in the URL path or as query/form parameters)","missing":["fw_name","hw_name","hw_version"],"conflicts":null,"what_strings":["1.9.0, 2026-03-01T10:00:00Z, AFI"],"instance":"/firmware","request_id":"…"}422 Unprocessable Entity: request fields disagree with the what string: fw_name: what string has "demo", request has "other"
403 Forbidden: missing scope fw:write
How upload fields are resolved
Each field is taken from the what string when it's there. Otherwise it comes from the
request, in this order: path, then form field, then query parameter; the firmware object's
fields_from_request lists which fields came from the request.
- If a field appears in both places and the values differ, the upload is rejected
(
422). Comparisons ignore case, and avprefix on versions. fw_version,fw_name,hw_name,hw_versionandfw_typemust all end up known;build_dateis optional.- If the binary contains several different what strings, choose one with
what_index(see Parse a binary). - A binary without any what string is rejected (
422).
Parse a binary
POST /firmware/parse
Bearer token · scope fw:read
A dry run: shows the what strings found in a binary and how they are read. Nothing is
stored. A what string that can't be parsed is listed with an error field.
Form parts (multipart/form-data)
firmwarefile requiredThe binary to inspect.
curl -X POST "https://fw.unitcircle.ca/firmware/parse" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ" \ --form-string 'firmware=@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI'
http --multipart -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ POST "https://fw.unitcircle.ca/firmware/parse" \ 'firmware=\@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI'
import requests
r = requests.post(
"https://fw.unitcircle.ca/firmware/parse",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
files={"firmware": ("demo.bin", "@(#)1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI")},
)
print(r.status_code, r.json())
{
"sha256": "394cccafdb7042d1e58a52aa76a392adaf8a41d2a1463783c26e14504499fa50",
"size_bytes": 60,
"what_strings": [
{
"build_date": "2026-09-26T12:00:00Z",
"format": "v2-5field",
"fw_name": "demo",
"fw_type": "AFI",
"fw_version": "1.9.3",
"hw_name": "example-hw",
"hw_version": "2.0.0",
"index": 0,
"version": "1.9.3",
"what_string": "1.9.3, demo, example-hw@2.0.0, 2026-09-26T12:00:00Z, AFI"
}
]
}
List firmware
GET /firmware
Bearer token · scope fw:read
Newest version first (use order=asc for oldest first), 100 per page.
Query parameters
fw_namestringExact firmware name.
hw_namestringExact hardware name.
hw_versionstringExact hardware version.
fw_typestringAFI,MFIorEFI.tagstringOnly builds with this tag.
statusstringactiveorwithdrawn.generalbooleantrue: general releases only;false: tagged or staged only.orderstringascfor oldest version first.limitinteger1–500, default 100.
offsetintegerNumber of items to skip; use
next_offsetfrom the previous page.
curl "https://fw.unitcircle.ca/firmware?fw_name=demo&limit=2" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ GET "https://fw.unitcircle.ca/firmware?fw_name=demo&limit=2"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware",
params={
"fw_name": "demo",
"limit": "2"
},
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
)
print(r.status_code, r.json())
{
"items": [
{
"id": "01a0db3a-18c0-7e7b-ae23-05cc81dd68e3",
"fw_name": "demo",
"hw_name": "example-hw",
"hw_version": "2.0.0",
"fw_version": "1.9.0",
"version": "1.9.0",
"fw_type": "AFI",
"tag_filter_disabled": false,
"status": "active",
"tags": [],
"…": "all other firmware fields"
},
{
"id": "01a0db3a-1883-73e4-b695-e6f21b5ade15",
"fw_name": "demo",
"hw_name": "example-hw",
"hw_version": "2.0.0",
"fw_version": "1.8.0",
"version": "1.8.0",
"fw_type": "AFI",
"tag_filter_disabled": false,
"status": "active",
"tags": [],
"…": "all other firmware fields"
}
],
"next_offset": 2
}
Retrieve firmware
GET /firmware/{id}
Bearer token · scope fw:read
Returns the firmware object with a weak ETag, which you can send back as If-Match when
updating.
Path parameters
idstring requiredFirmware id.
curl "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ GET "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
)
print(r.status_code, r.json())
{
"id": "01a0db38-b3fb-7af4-b46b-638aec828af5",
"fw_name": "demo",
"hw_name": "example-hw",
"hw_version": "2.0.0",
"fw_version": "1.6.0-10-g30d0049",
"version": "1.6.0-10-g30d0049",
"fw_type": "AFI",
"build_date": "2025-12-05T20:25:18Z",
"what_string": "1.6.0-10-g30d0049, demo, example-hw@2.0.0, 2025-12-05T15:25:18-05:00, AFI",
"what_format": "v2-5field",
"fields_from_request": [],
"size_bytes": 4096,
"sha256": "04be2a18a2f8e1832bd08e9e631c8bd7db95c6dc09a4c3927a6938f907659562",
"notes_size": 110,
"notes_sha256": "5b3e…",
"notes_filename": "README.md",
"tag_filter_disabled": true,
"status": "active",
"uploaded_at": "2026-09-26T00:58:47.102Z",
"uploaded_by": "sandbox:seed",
"download_count": 3,
"notes_download_count": 1,
"last_accessed_at": "2026-09-26T00:59:30Z",
"updated_at": "2026-09-26T00:58:47.102Z",
"tags": []
}{"type":"about:blank","title":"Not Found","status":404,"detail":"firmware not found","instance":"/firmware/01a0d9be-0000-7000-8000-000000000000","request_id":"…"}
Update release settings
PATCH /firmware/{id}
Bearer token · scope fw:write
Changes who a build is offered to — never its content. Set its tags (replaces the whole
set; devices sharing a tag are offered the build), make it a general release
(tag_filter_disabled: true, offered to every device) or take it back to tag-only
delivery, and withdraw or reactivate it (status). A withdrawn build is never offered and
its download links stop working; reactivating it makes it available again.
Path parameters
idstring requiredFirmware id.
Headers
If-MatchstringThe
ETagyou retrieved;412if the firmware changed since.
Body parameters (JSON)
tagsarray of stringsReplaces the build's tags. Tags are created as needed.
tag_filter_disabledbooleantrue= general release (offered to every device);false= only devices sharing a tag.statusstringactiveorwithdrawn.
curl -X PATCH "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689" \
-H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA" \
-H "Content-Type: application/json" \
-d '{"tag_filter_disabled":true,"tags":["beta"]}'http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA PATCH "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689" \ 'tag_filter_disabled:=true' \ 'tags:=["beta"]'
import requests
r = requests.patch(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"},
json={
"tag_filter_disabled": True,
"tags": [
"beta"
]
},
)
print(r.status_code, r.json())
{
"id": "01a0db38-b3fb-7af4-b46b-638aec828af5",
"fw_name": "demo",
"version": "1.6.0-10-g30d0049",
"fw_type": "AFI",
"tag_filter_disabled": true,
"status": "active",
"tags": ["beta"],
"updated_at": "2026-09-26T01:00:28.204287Z",
"…": "all other firmware fields"
}{"type":"about:blank","title":"Bad Request","status":400,"detail":"status must be 'active' or 'withdrawn'","instance":"/firmware/01a0db38-b3fb-7af4-b46b-638aec828af5","request_id":"…"}{"type":"about:blank","title":"Precondition Failed","status":412,"detail":"firmware was changed by someone else; reload and retry","instance":"/firmware/01a0db38-b3fb-7af4-b46b-638aec828af5","request_id":"…"}
Download the binary
GET /firmware/{id}/binary
Bearer token · scope fw:read
Redirects (302) to a download link that is valid for 5 minutes.
Following it downloads a file named like demo-example-hw-2.0.0-1.6.0-10-g30d0049-AFI.bin.
Path parameters
idstring requiredFirmware id.
curl -L -o download.bin "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/binary" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
http --follow --download -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ GET "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/binary"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/binary",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
)
r.raise_for_status() # requests follows the redirect to the file
open("download.bin", "wb").write(r.content)
HTTP/1.1 302 Found Location: https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwEBoNs6GMB-e64jBcyB3WjjAAAAAGq3GliVRhyw2lQ36w.Ek5wjL5qLh0sgeMoOeWl9ASyU1s Cache-Control: no-store <!doctype html> … Redirecting... …
Download the release notes
GET /firmware/{id}/notes
Bearer token · scope fw:read
Like the binary, but for the notes (…-notes.md or …-notes.txt).
Path parameters
idstring requiredFirmware id.
curl -L -o download.bin "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/notes" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
http --follow --download -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ GET "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/notes"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/notes",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
)
r.raise_for_status() # requests follows the redirect to the file
open("download.bin", "wb").write(r.content)
HTTP/1.1 302 Found Location: https://fw.unitcircle.ca/dl/AaDZvRgieMa7-iOfg8tIIwIBoNs6GMB-e64jBcyB3WjjAAAAAGq3Glg3cJ7… Cache-Control: no-store
List a build's tags
GET /firmware/{id}/tags
Bearer token · scope fw:read
Path parameters
idstring requiredFirmware id.
curl "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ GET "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags"
import requests
r = requests.get(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"},
)
print(r.status_code, r.json())
{"tags": ["beta"]}
Replace a build's tags
PUT /firmware/{id}/tags
Bearer token · scope tags:write
Devices that share one of these tags are offered the build (in addition to everyone, if it is a general release). Tags are created on first use.
Path parameters
idstring requiredFirmware id.
Body parameters (JSON)
tagsarray of strings requiredThe complete new tag set.
curl -X PUT "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags" \
-H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA" \
-H "Content-Type: application/json" \
-d '{"tags":["beta","qa"]}'http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA PUT "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags" \ 'tags:=["beta","qa"]'
import requests
r = requests.put(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"},
json={
"tags": [
"beta",
"qa"
]
},
)
print(r.status_code, r.json())
{"tags": ["beta", "qa"]}{"type":"about:blank","title":"Bad Request","status":400,"detail":"invalid tag \"bad tag!\" (letters, digits, . _ : -; max 64)","instance":"/firmware/01a0db38-b3fb-7af4-b46b-638aec828af5/tags","request_id":"…"}
Add a tag to a build
PUT /firmware/{id}/tags/{tag}
Bearer token · scope tags:write
Idempotent.
Path parameters
idstring requiredFirmware id.
tagstring requiredLetters, digits,
._:-; up to 64 characters.
curl -X PUT "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"
http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA PUT "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc"
import requests
r = requests.put(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"},
)
print(r.status_code, r.text)
(no body)
Remove a tag from a build
DELETE /firmware/{id}/tags/{tag}
Bearer token · scope tags:write
Idempotent.
Path parameters
idstring requiredFirmware id.
tagstring requiredTag name.
curl -X DELETE "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc" \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"
http -A bearer -a eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA DELETE "https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc"
import requests
r = requests.delete(
"https://fw.unitcircle.ca/firmware/01a0f8ad-87f3-74b1-8172-257e904c8689/tags/rc",
headers={"Authorization": "Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA"},
)
print(r.status_code, r.text)
(no body)