uclogserver
Log in

Getting started

uclogserver stores firmware images and device log files for hardware makers, and tells each of your devices which firmware updates it may install. Everything is available through a REST API; a web console handles day-to-day management.

This page explains the main ideas and takes you through a five-command tour against the public sandbox. You don't need an account for the tour. The API reference has every endpoint, with cURL, HTTPie and Python examples and a Try it button that runs requests against the sandbox from your browser.

What the service does

You want to… The service gives you…
Publish a firmware build and its release notes An upload endpoint that reads version, product and type from the build's embedded what string
Let devices find out if there's something newer An unauthenticated update-check URL that returns newer builds with 5-minute download links
Roll out a beta to some devices only Tags on firmware and devices; a device sees a tagged build only if it shares a tag with it
Collect log or crash files from devices or test rigs Upload by a 512-bit hex name; download later through a short-lived link
Keep a registry of manufactured devices Device records keyed by hardware id, with optional Ed25519 public keys and tags
Automate all of the above from CI Scoped, expiring API tokens

Key concepts in one minute

  • Account. Your organisation is identified by a domain name such as example.com. Your API token belongs to your account, so URLs like /firmware or /devices automatically mean your firmware and devices. Only the public update check names the account in the URL. Each account's data is completely separate. See Concepts.
  • Firmware. Each build is identified by firmware name, hardware name, hardware version, version and type — AFI (application), MFI (manufacturing) or EFI (engineering). The service reads these from the @(#) what string embedded in the binary; you supply any that are missing.
  • Versions. Semver-like with two extensions: git-describe builds (1.4.1-10-g30d0049 is later than 1.4.1) and an optional 4th number (1.5.1.2 is later than 1.5.1).
  • General release vs tagged release. A new upload is staged: no device can see it yet. Make it a general release to offer it to everyone, or give it tags to offer it only to devices with a matching tag.
  • Devices. A device is identified by its HW id: 1–64 printable ASCII characters without spaces (e.g. SN-000123 or a1b2c3d4), case-sensitive. It may carry tags and two Ed25519 public keys. Devices are never deleted; they are withdrawn.
  • Write-once. Firmware builds and log files never change and are never deleted; re-sending identical content is harmless, different content is a 409.
  • Log data. Arbitrary binary files, each named by a 128-hex-character (512-bit) identifier.
  • Download links. Files are never served from permanent URLs; the service hands out links that expire after 5 minutes (see Download links).

The sandbox

The sandbox is a real account, sandbox.example, with two publicly published API tokens. The examples in these docs already contain the server address and these tokens, so you can copy them into a terminal and run them as they are.

Token Scopes
read-only fw:read logdata:read devices:read tags:read stats:read
write read-only plus fw:write logdata:write devices:write tags:write

Read-only token:

eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ

Write token:

eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA

The sandbox is wiped and re-seeded every day at 00:30 UTC. After a reset it contains:

Kind Items
Firmware demo for example-hw@2.0.0 1.4.1-10-g30d0049 AFI, v1.5.1.2 AFI, 1.6.0-10-g30d0049 AFI and EFI (all general releases); 1.7.0-rc.1 AFI tagged beta
Devices a1b2c3d4 (tag beta, has an identity key), a1b2c3d4e5f60718 (tag production), 0123456789abcdef01234567 (no tags)
Log data one file named c309d264…4710

The tokens are public, so anything they can do is open to everyone (uploading, tagging, withdrawing; the seeded data comes back at the next reset). They can't manage members and tokens, and uploads are limited to 1 MiB per firmware image and 256 KiB per log file. Don't upload anything private. To try member and token management, ask for an evaluation account.

Five-command tour

1. Is the service up?

curl -s https://fw.unitcircle.ca/status | jq .status
"ok"

2. What updates would a device running 1.5.1 get? The update check needs no token.

curl -s https://fw.unitcircle.ca/firmware/sandbox.example/example-hw/2.0.0/demo/1.5.1 | jq '.[] | {version, "fw-type"}'
{ "version": "example-hw/2.0.0/demo/1.5.1.2", "fw-type": "AFI" }
{ "version": "example-hw/2.0.0/demo/1.6.0-10-g30d0049", "fw-type": "AFI" }

3. The same question for the beta-tester device a1b2c3d4. The device's HW id is the last part of the URL. It also gets the 1.7.0-rc.1 build tagged beta:

curl -s https://fw.unitcircle.ca/firmware/sandbox.example/example-hw/2.0.0/demo/1.6.0-10-g30d0049/a1b2c3d4 | jq '.[].version'
"example-hw/2.0.0/demo/1.7.0-rc.1"

4. Upload a firmware build. Normally you send your build output (-F firmware=@build/demo.bin); the service reads the embedded what string. For a quick try, the "binary" can be just a what string, sent inline — no files needed. This one has no product names, so the path supplies them:

curl -s -X PUT https://fw.unitcircle.ca/firmware/example-hw/2.0.0/demo \
     -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCB3cml0ZXIgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkxNC03MjcxLWE4MGEtMjI3NzYyYWY5NjliIiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCBmdzp3cml0ZSBsb2dkYXRhOndyaXRlIGRldmljZXM6d3JpdGUgdGFnczp3cml0ZSJ9.AdKQR9H96-zqRYvWVf7ALksV1UAht2CzSHdomiepLAfuVjFOPiTowNWssFRgmMuu9z7CHTdug1AcY6jWiMW6DA" \
     --form-string 'firmware=@(#)9.9.9, 2026-01-01T00:00:00Z, AFI' \
     --form-string 'notes=# 9.9.9

* My first upload.'
OK

Add -H 'Accept: application/json' to get the stored firmware record back as JSON instead of OK (see Response formats).

5. Download a log file. You get a 302 to a link that stops working after 5 minutes; -L follows it:

curl -sL -o log.bin -w '%{http_code} after %{num_redirects} redirects\n' \
     https://fw.unitcircle.ca/logdata/c309d264c3d86dabc64d34790b50cd9780ab94eb274456ff66e70b0e4cecc6182c008bacf71cdd3201a14a78c1df62cb794f54b6c60e26463f88ee1e01e74710 \
     -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMTAtMDEtYjItaiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2Z3LnVuaXRjaXJjbGUuY2EiLCJzdWIiOiJzdmM6c2FuZGJveCByZWFkLW9ubHkgKHB1YmxpYykiLCJhdWQiOlsidWNsb2dzZXJ2ZXIiXSwiZXhwIjoxODIyNDE0NTE3LCJuYmYiOjE3OTA4Nzg1MTIsImlhdCI6MTc5MDg3ODUxNywianRpIjoiMDFhMGY4YWQtODkwZi03ODA5LWI3YTgtMDE0MTZlOWM0YTI3IiwidHlwIjoiYXBpIiwiY3VzdCI6InNhbmRib3guZXhhbXBsZSIsImNpZCI6IjAxYTBmOGExLTEzYWMtN2QzNC05NDEyLTMwYjAwOGFkYzk4NyIsInNjcCI6ImZ3OnJlYWQgbG9nZGF0YTpyZWFkIGRldmljZXM6cmVhZCB0YWdzOnJlYWQgc3RhdHM6cmVhZCJ9.IaQSEW8c2zmJWEioQ8PBaezftMriGUzGQhIof_T7HGG4Usjt9dRmhj-xiw_tMuEYcsR7EzLzXyELG0DrdJGADQ"
200 after 2 redirects

Prefer clicking? Every endpoint in the API reference has a Try it button that runs the request against the sandbox.

How the URLs are organised

There is no version prefix.

URLs Who calls them Account comes from
GET /firmware/{domain}/{hw}/{hwver}/{fw}/{fwver}[/{hwid}] devices (no token) the {domain} in the path
/firmware…, /logdata…, /devices…, /tags…, /tokens…, /members…, /stats/… your tools, with a token your token
/admin/… the service's administrators ?customer= or the path
/status, /healthz, /readyz, /health/history, /openapi.json monitoring, anyone –

Where next