Relayer logoRelayer

Push-mode fleets

Manage fleets where your orchestrator pushes updates and devices never call home directly.

Pull mode (GET /u/…) assumes each device checks in on its own. Plenty of fleets don't work that way: routers, firmware, kiosks and agents behind a management plane, where your orchestrator talks to the devices and pushes updates to them. In push mode the orchestrator speaks to Relayer on the devices' behalf - Relayer still owns the decisions (rollout cohorts, channel policy, rollback), your system owns the transport.

Both endpoints are API-key authenticated and accept up to 500 devices per call, so a whole wave costs one request against the rate limit instead of one per device.

Report fleet state

POST /api/v1/fleet/report - a heartbeat on the devices' behalf. Devices appear in the fleet dashboard, adoption charts and the live feed exactly as if they had checked in themselves.

curl -X POST https://relayer-three.vercel.app/api/v1/fleet/report \
  -H "Authorization: Bearer rl_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app": "YOUR_APP_REF",
    "devices": [
      {
        "deviceId": "router-0042",
        "version": "1.4.0",
        "platform": "linux",
        "arch": "aarch64",
        "labels": { "customerRef": "acme-42", "siteRef": "blr-dc1" }
      }
    ]
  }'

Devices are upserted on (app, deviceId) - report as often as you like. channel defaults to stable.

Labels

labels are opaque key/values (max 16 per device, keys ≤ 64 chars, values ≤ 256): Relayer stores and echoes them - in the devices table and API responses - but never interprets them. Use them to correlate Relayer's records with your own tenant hierarchy (customerRef, siteRef, routerName, …). A report without labels never clears labels a previous report attached; sending labels replaces them.

Plan an update wave

POST /api/v1/fleet/decisions - hand Relayer the fleet's current state, get back one decision per device. This runs the exact same logic as GET /u/ (same code, not a copy): version comparison, staged-rollout bucketing with fall-through, artifact matching and channel policy.

curl -X POST https://relayer-three.vercel.app/api/v1/fleet/decisions \
  -H "Authorization: Bearer rl_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app": "YOUR_APP_REF",
    "channel": "stable",
    "devices": [
      { "deviceId": "router-0042", "version": "1.4.0", "platform": "linux", "arch": "aarch64" },
      { "deviceId": "router-0043", "version": "1.5.0", "platform": "linux", "arch": "aarch64" }
    ]
  }'
{
  "app": "YOUR_APP_REF",
  "channel": "stable",
  "decisions": [
    {
      "deviceId": "router-0042",
      "update": {
        "version": "1.5.0",
        "url": "https://your-storage/agent-1.5.0-linux-aarch64.bin",
        "sha512": "…",
        "updateMode": "recommended",
        "notes": "## 1.5.0 …",
        "pub_date": "2026-07-01T10:00:00.000Z"
      }
    },
    { "deviceId": "router-0043", "update": null }
  ],
  "updatesOffered": 1
}

update: null means up to date, outside the current rollout cohort, or no artifact for that platform/arch - the same cases where /u/ answers 204. Your orchestrator pushes the artifact to each device with a non-null decision, then confirms with a follow-up report once the device is on the new version.

Dry runs

Add "dryRun": true to plan a wave without writing anything - no device upserts, no check-ins. Useful for previewing how many devices a rollout percentage would reach before you commit.

Semantics worth knowing

Staged rollouts behave identically in both modes: a device outside a 25% cohort falls through to the newest fully-rolled-out release, and cohort membership is sticky (sha256(deviceId:releaseId) % 100). Channel minVersion policy flags decisions updateMode: "required" for devices below the minimum. Rolled-back releases are never offered. Mixing modes is fine - the same device can check in via /u/ today and be reported by an orchestrator tomorrow; it's one row keyed by (app, deviceId).

Errors

StatusMeaning
400Validation failed (the message names the field, e.g. devices.0.version)
401Invalid or revoked API key
404Unknown app or channel (or app not in your organization)
429Rate limit exceeded

On this page