Relayer logoRelayer

Device updates

The update-check contract: one GET request, staged rollouts, update policies.

The contract

GET /u/<appRef>/<channel>/<platform>/<arch>/<currentVersion>
HeaderPurpose
X-Relayer-DeviceAnonymous UUID, generated and persisted by your app. Enables sticky rollout bucketing and the fleet dashboard. Optional but strongly recommended.

Optional query parameters: l.<key>=<value> attaches targeting labels to the check (see Label targeting below), e.g. ?l.region=eu&l.tier=canary.

Responses:

  • 200 - update available; JSON manifest below.
  • 204 - up to date, no matching artifact, or this device is not in the rollout cohort yet. Either way: do nothing, check again later.
{
  "version": "1.5.0",
  "notes": "markdown release notes",
  "pub_date": "2026-07-12T10:00:00Z",
  "url": "https://your-storage/…/app-darwin-arm64.dmg",
  "sha512": "…",
  "signature": "…",
  "updateMode": "recommended",
  "minimumSupportedVersion": "1.2.0",
  "allowDowngrade": true
}

Version comparison is semver-aware (prereleases handled). Artifact matching: exact platform+arch first, then platform+universal, then platform-only. allowDowngrade appears (as true) only on rollback fallbacks - the device's current version was pulled and this manifest points below it.

Try it

# one device, currently on 1.0.0
curl -H "X-Relayer-Device: $(uuidgen)" \
  https://www.relayercli.com/u/YOUR_APP_REF/stable/darwin/aarch64/1.0.0

# simulate a small fleet (each device = a distinct UUID)
for i in $(seq 1 10); do
  curl -s -o /dev/null -H "X-Relayer-Device: $(uuidgen)" \
    https://www.relayercli.com/u/YOUR_APP_REF/stable/darwin/aarch64/1.0.0
done

Staged rollouts

Every release has a rollout percentage, controlled from the Releases tab (1 → 10 → 25 → 50 → 100 quick steps, or any value). Devices are bucketed deterministically - sha256(deviceId:releaseId) % 100 - so cohorts are sticky: a device offered 2.1.0 keeps being offered 2.1.0; a device outside the cohort keeps getting the previous release until you ramp.

The percentage is monotonic once the release has been offered: from the first real offer it can only increase. Buckets are sticky, so lowering the number would never un-serve anyone - it would only make the dashboard lie about the field. To stop offering, pause; to pull the release, roll back. The API answers attempts to decrease with 422. Before the first offer, the percentage is just a plan and moves freely in both directions.

Devices that don't send X-Relayer-Device can't be bucketed and sit out partial rollouts - they only receive fully-rolled-out (100%) releases.

Label targeting

A release can carry targeting rules: label keys with allowed values, AND across keys, any-of within a key (region=eu|uk AND tier=canary). Set them per release with Target on the Releases tab; the editor suggests keys and values already observed in your fleet. The rollout percentage then applies within the targeted cohort.

Labels reach Relayer three ways, merged key-by-key with the check's own labels winning: l.<key>=<value> query params on /u/ checks, inline labels on push-mode decisions calls, and labels stored earlier by push-mode report. Devices missing a targeted key fail closed on that release and fall through to the newest untargeted release below it - targeting a canary build never breaks the rest of the fleet.

Like the percentage, targeting is monotonic once a release has been offered: it can only broaden (add values, drop keys), and broadening is itself permanent - you cannot narrow back afterwards. The editor warns before you cross that line. Narrowing needs a rollback and a new release; before the first offer, targeting is freely editable.

Pause and rollback

Pause stops serving a release temporarily; devices already on it stay, nobody new is offered it, and Resume picks the rollout up where it stopped.

Roll back withdraws a release permanently. Devices already ON the rolled-back version are then served the newest still-published release below it as a downgrade: the manifest carries "allowDowngrade": true and its update mode is floored at recommended. The Releases tab shows how many devices are still stranded on a rolled-back version and exactly what they fall back to - the rollback confirm dialog shows the same before you commit. Clients must accept down-version manifests for this to work: the Relayer CLI does natively, electron-updater needs its allowDowngrade flag, and Tauri's updater refuses downgrades (publish a higher-numbered re-release instead, e.g. 2.1.1 with 2.0.0 contents).

If no published release exists below the rolled-back one, those devices hold until you publish something. Rollbacks cannot be un-done by design.

Update policies

Per channel, set a minimum supported version. Devices reporting a version below it receive the update with "updateMode": "required" and the minimumSupportedVersion field - your app decides how to enforce it (block UI, force install, nag). Everything else gets your channel's default mode (optional or recommended).

Client loop (any language)

on schedule (e.g. every 6h + on launch):
  res = GET /u/APP/stable/{platform}/{arch}/{installed_version}
         with X-Relayer-Device: {stored_uuid}
  if res is 204: done
  if res is 200:
    verify sha512/signature after download
    if updateMode == "required": install now
    else: prompt or install per your UX

Ten lines in any runtime. If you ship Tauri or Electron, you don't even need that - see Adapters.

Why this update?

The Releases tab includes a decision preview that runs the exact serving walk for a real device id (using its stored labels and rollout bucket) or a hypothetical device you describe. It shows every release considered and why each was skipped - outside the rollout bucket, targeting mismatch, no artifact for the platform - plus the manifest the device would receive. The preview and the serving endpoints share one code path, so what it says is by construction what devices get. Each device's detail sheet also shows a "Next check" line answering the same question per device.

On this page