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
| Status | Meaning |
|---|---|
400 | Validation failed (the message names the field, e.g. devices.0.version) |
401 | Invalid or revoked API key |
404 | Unknown app or channel (or app not in your organization) |
429 | Rate limit exceeded |