Platform / Fleet
Zero-touch provisioning and the claim flow
Pre-register robots in bulk, hand the factory one reusable claim token, and let each unit register itself on first boot. A claim token is never itself an API credential: every claim mints a credential unique to the robot that presented it.
01 Three ways to register a robot
detailsIn the browser
/app/fleet
The Register robot action creates a single robot from the fleet board. No API call required.
CSV pre-registration
up to 1,000 rows
Upload a CSV on /app/fleet, or POST parsed rows to the batch endpoint. Each row becomes a pending robot with no service agent linked.
Self-registration
claim token
The unit calls POST /api/v1/enrollments/claim on first boot with its serial number and receives its own credential.
02 CSV columns
detailsserial_number is the only required column
Optional columns: name (defaults to the serial number), model, manufacturer, robot_type, cohort (an existing cohort name), and tags (comma or semicolon separated). Headers are matched case- and separator-insensitively, so "Serial Number" works; unknown columns are ignored. Files are capped at 1,000 rows and 2 MB.
Per-row results, not an all-or-nothing batch
The response reports a summary of total, created, and skipped, plus one result row per input row carrying row, serial_number, status, and -- when skipped -- a reason. Skip reasons are invalid, duplicate_in_batch, duplicate_serial, unknown_cohort, and quota_exceeded. A per-row quota_exceeded still returns 201 for the batch as a whole.
03 Bulk pre-registration response
examplePOST /api/v1/operator/robots/batch
{"robots": [
{"serial_number": "RC-0001", "model": "MR-2", "cohort": "Rotterdam DC"},
{"serial_number": "RC-0002"},
{"serial_number": "RC-0001"}
]}
Response 201:
{
"summary": {"total": 3, "created": 2, "skipped": 1},
"robots": [{"id": "...", "serial_number": "RC-0001", "status": "pending"}],
"results": [
{"row": 1, "serial_number": "RC-0001", "status": "created"},
{"row": 2, "serial_number": "RC-0002", "status": "created"},
{"row": 3, "serial_number": "RC-0001", "status": "skipped",
"reason": "duplicate_in_batch", "errors": {}}
]
}
04 Fleet claim tokens
reference| Field | Meaning |
|---|---|
| max_claims | How many times the token may be claimed. Clamped to 1-1000; above 1 the token is a fleet claim token and no service agent is created up front. |
| expires_in_minutes | Lifetime from creation, clamped to 5-1440, default 30. The response carries the resolved expires_at. |
| serial_allowlist | Serial numbers permitted to claim. Up to 1,000 entries of at most 120 characters. Empty means any serial is accepted. |
| cohort_id | Cohort every claimed unit joins. Cleared if that cohort is later deleted. |
| default_tags | Tags applied to every claimed unit, lowercased and trimmed. |
| kind / scope_preset / scopes | agent or robot; read_only, worker, or robot preset, or an explicit scope list. |
05 What a claim does
detailsSerial binding
If the presented serial matches a pending pre-registered robot, the claim binds to that row instead of creating a duplicate, links a newly created service agent, moves the robot out of pending, and applies the token's cohort and default tags.
Unique credentials per claim
Each claim returns an api_key and a 30-day access_token minted for that robot alone. The claim token itself never authenticates an API request, and the claim response deliberately omits serial_allowlist so one unit cannot enumerate its siblings.
Token status
Reported status resolves in order: revoked, then expired, then claimed (a single-use token that has been used), then exhausted, then active. A revoked token that was previously claimed reports revoked.
06 Claim request and response
examplePOST /api/v1/enrollments/claim
{
"token": "age_xxxx.yyyy",
"serial_number": "RC-0001"
}
Response 201:
{
"enrollment": {"id": "...", "kind": "robot", "status": "active", "claims_remaining": 299},
"service_agent": {"id": "...", "name": "RC-0001", "status": "active"},
"robot": {"id": "...", "serial_number": "RC-0001", "status": "offline"},
"api_key": {"key": "agk_xxx.yyy", "credential_id": "...", "type": "api_key"},
"access_token": {"token": "SFMyNTY...", "type": "bearer", "expires_in": 2592000}
}
07 Claim error responses
reference| Status | detail / error | When |
|---|---|---|
| 400 | serial_not_allowed | Serial is not on the token's allowlist |
| 400 | serial_already_claimed | That serial's robot is already bound to another identity |
| 400 | serial_number_required | A fleet claim token was presented without a serial number |
| 401 | Invalid enrollment token | Bad format, unknown prefix, or digest mismatch |
| 409 | enrollment revoked | The token was revoked |
| 409 | enrollment already claimed | A single-use token was already used |
| 409 | enrollment exhausted | A fleet token has no claims left |
| 410 | This enrollment token has expired | Past expires_at |
| 429 | quota_exceeded | The workspace is at its robots plan limit |
| 403 | workspace_frozen / workspace_paused / workspace_archived | The workspace is not accepting machine traffic; nothing is created and the denial is audited |
08 Robot lifecycle
detailsStatuses
pending, online, offline, charging, error, maintenance, decommissioned. maintenance is still in service. pending means the row exists and is cohortable and claimable, but no machine has presented an identity yet.
decommissioned is terminal
Decommissioning revokes the robot's credentials in the same transaction that sets the status, so its API key and any outstanding access token stop authenticating. The row and its history are kept, it is hidden from listings unless asked for explicitly, it stops consuming the robots quota, and bulk operations skip it with skip_reason robot_decommissioned.
What decommissioning does not do
It is a filter on the surfaces that act on robots, not a cascade. Static cohort membership rows survive and are still returned by the cohort show endpoint; only roster and rule-match reads exclude the robot. Automatic mission assignment picks from online robots only and will never choose it, but an explicit assign call still succeeds -- it checks that the robot exists in the workspace, not that it is in service. Treat the 410 on device writes as the hard boundary.
Device writes after decommission
Heartbeat, diagnostics, and telemetry writes return 410 Gone with type https://robotscenter.net/problems/robot-decommissioned, so a still-powered unit cannot put itself back on the roster.
Related docs
see also