Fleet APIs let operators manage the selected workspace's physical or hosted robots. They combine registration and grouping with mission state, health evidence, maintenance predictions, OTA status, and auditable batch progress.
Inventory is not presence
A robot record describes identity and configuration. last_seen_at, heartbeat data, diagnostics, battery, fleet alerts, and maintenance predictions provide operational health; do not infer readiness from status alone.
Bulk work is asynchronous evidence
Batch operations save per-target command links and progress. For dispatch_command, queued or accepted delivery is not device success: targets remain running until an exact terminal command result. A cancellation request stops known-unsent work but cannot guarantee a command already sent to a robot has physically stopped. Progress PubSub is emitted after commit on a best-effort basis; poll persisted batch/target state after reconnect.
Current location and historical evidence differ
Robot current position and geofence boundary state are separate from append-only location samples. A trusted heartbeat location must be no more than five minutes old or 30 seconds ahead. Historical pages use a signed workspace/robot-bound cursor (25 default, 100 max); old samples expire after 90 days by default without clearing current position, active alerts or transition_version.
Work-order history survives detail retention
Only in_progress work orders reserve a robot. Completed/cancelled order summaries keep outcome, parts, completion actor and alert/prediction links. Recent /events rows age out after 400 days by default, but the response's archived_audit_url leads to paginated immutable audit counterparts keyed by original work_order_event_id. Audit history has no automatic time purge.
Before you begin
A signed-in browser session with a selected workspace backed by an active membership is required; an archived workspace remains readable but rejects mutations. Every /api/v1/operator endpoint rejects API credentials and bearer tokens, regardless of their scopes.
Any role on an active membership can read fleet state. Owners, admins, and members can operate the fleet; auditors cannot register, dispatch, acknowledge, update, or decommission.
Use stable serial numbers and verify the robot, cohort, mission, or update belongs to the selected workspace before acting.
How it works
Build a safe target set
Filter robots by site, tag, status, or attention signal and inspect health before dispatch. Cohorts give reusable membership; batch operations give a durable snapshot of the chosen targets.
Observe after action
After a mission, OTA, alert action, or batch starts, follow its detail endpoint and per-target outcome. An accepted mission or OTA command is still outcome unknown until matching robot ACK. For geofences, hydrate current state first, then accept only strictly newer transition_version for the same fence/revision. Compare fresh heartbeat, diagnostics, and fleet-alert summaries before declaring the operation complete.
Examples
These examples are illustrative. Replace the sample values with your own.
Keep credentials in environment variables or a secret manager, never in client-side code.
List robots needing attention
The list stays workspace-scoped; returned robot summaries include identity, lifecycle, battery, location, tags, and last-seen data.
javascript
const response = await fetch("/api/v1/operator/robots?filter%5Bstatus%5D=offline", {
credentials: "same-origin",
headers: {"Accept": "application/json"}
});
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${body.detail}`);
console.log(body);
What to expect
200 OK with {"robots": [], "next_cursor": null} when no offline robots match. A populated robot row includes serial_number, status, battery_level, location, tags, and last_seen_at. Pass next_cursor unchanged for the next bounded page.
Troubleshooting
A valid robot ID returns 404
Fleet lookups never cross workspace boundaries. Check the selected workspace; a foreign identifier is deliberately reported as not found.
An operation partly failed
Open the batch or OTA status and inspect target-level reasons. Retry only failed targets when supported, rather than issuing a second broad operation that repeats successful work.
Endpoint reference
Ready to integrate? Open an endpoint for its required permissions, request fields,
response format, and status codes. The examples above explain the workflow;
these details define each individual request.
GET
/api/v1/operator/robots
Web Session
List robots
Description
Returns a name/id-ordered page of robots in the current workspace (default 25, maximum 100). Follow next_cursor with the same filters for continuation. A mutable robot name can move between pages; this is not a frozen roster snapshot.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
query
query
string
No
Case-insensitive search in name, serial, manufacturer, or model
status
query
string
No
Filter by robot status. Decommissioned robots are hidden unless `status=decommissioned` is requested explicitly.
robot_type
query
string
No
Filter by robot type
cohort_id
query
string
No
Restrict to entities whose robot belongs to this cohort, statically or by rule
tag
query
string
No
Restrict to entities whose robot carries this tag (lowercased and trimmed). Exactly one tag; a value that does not resolve to a single tag matches nothing rather than being ignored.
limit
query
integer
No
Page size (default 25, maximum 100)
cursor
query
string
No
Opaque continuation token, bound to workspace and effective filters
Creates one `pending` robot per serial number, with no service agent linked. Rows are independent: a duplicate serial, an invalid row, an unknown cohort name or a row past the plan's robot quota is reported against its own row number while the rest of the batch still lands. Maximum 1000 rows per request.
Auth
Web Session
Request Body
Field
Type
Required
Description
robots
array<object>
Yes
Rows to pre-register. Each row needs `serial_number`; `name`, `model`, `manufacturer`, `robot_type`, `cohort` (an existing cohort name) and `tags` are optional. The name defaults to the serial number.
Retires a robot permanently by setting its status to `decommissioned` and revoking the robot identity's credentials (the `agk_` key and any outstanding bearer token stop authenticating immediately, so a still-powered unit cannot report its way back onto the roster). The row is never deleted — mission, diagnostic and alert history stay intact and the robot is still readable by id — but it disappears from robot listings (unless `status=decommissioned` is requested), from mission auto-assignment and alert evaluation, and it stops counting against the plan's robot quota. This is distinct from `maintenance`, which is an in-service state.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Robot UUID
Responses
204
Robot decommissioned
404
Robot not found
POST
/api/v1/operator/robots/:id/heartbeat
Web Session
Record robot heartbeat
Description
Records a robot heartbeat. Numeric lat/lng location is validated, appended to bounded history, and evaluated against active geofences atomically; optional observed_at is ISO8601, at most five minutes old or 30 seconds future, and strictly newer than the previous trusted fix. Omitted location preserves the trusted fix. `last_seen_at` is server time. Claimed robots move to `online`; unclaimed `pending` pre-registrations stay `pending`. The body may be wrapped in `heartbeat` or sent flat.
Auth
Web Session
Request Body
Field
Type
Required
Description
heartbeat
object
No
Optional wrapper. The same fields are accepted at the top level when the wrapper is omitted.
heartbeat.metadata
object
No
Arbitrary metadata
heartbeat.location
object
No
Trusted numeric lat/lng, optional observed_at and bounded scalar extras
heartbeat.firmware_version
string
No
Current firmware version
heartbeat.battery_level
integer
No
Current battery level (0-100). Values outside that range return 422.
The robot has been decommissioned and can no longer report
422
Validation error (invalid battery_level or other changeset errors)
GET
/api/v1/operator/missions
Web Session
List missions
Description
Returns missions in the current workspace newest first, with optional status and robot filters. Always bounded: `limit` defaults to 100 and is clamped to 500.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
status
query
string
No
Filter by mission status
robot_id
query
string
No
Filter by robot UUID
cohort_id
query
string
No
Restrict to entities whose robot belongs to this cohort, statically or by rule
tag
query
string
No
Restrict to entities whose robot carries this tag (lowercased and trimmed). Exactly one tag; a value that does not resolve to a single tag matches nothing rather than being ignored.
limit
query
integer
No
Maximum missions to return, newest first (default 100, server maximum 500)
Creates a new mission in the current workspace. A new mission is always `pending`: lifecycle fields (`status`, `trace_id`, `result`, `started_at`, `completed_at`) are ignored and only change through the assign/start/pause/resume/complete/fail/cancel endpoints.
Auth
Web Session
Request Body
Field
Type
Required
Description
mission
object
Yes
Mission attributes
mission.name
string
Yes
Mission name (2-200 characters)
mission.priority
string (low, normal, high, critical)
No
Priority level
mission.description
string
No
Mission description
mission.payload
object
No
Mission-specific payload. Auto-assign reads `payload.required_capabilities` (an array of strings) and prefers online robots whose capabilities overlap.
mission.scheduled_at
datetime (ISO 8601)
No
Scheduled start time. ScheduledMissionWorker queues eligible pending or assigned missions after this time; robot acknowledgment confirms actual start.
mission.robot_id
string
No
Robot UUID to assign
mission.waypoints
object
No
Mission waypoints as `{"waypoints": [...]}`. Each waypoint is an object with a numeric `lat` (-90 to 90) and `lng` (-180 to 180), an optional `action` string and an optional `params` object; anything else is refused with 422.
Queues fleet.mission.execute for a fresh online, linked robot. Returns assigned/queued with command_id and a trace; only a robot ACK moves it to in_progress/running. Queued commands wait for reconnect and are not duplicated on repeat start. An accepted command is never automatically re-executed after transport loss.
Queues fleet.mission.cancel for active robot work; status stays unchanged until robot success ACK. A queued execute is cancelled locally only when delivery_state is known_unsent. Legacy unknown or possibly_delivered queued executes instead receive a pending device cancel; original execute redelivery is fenced and a late bound ACK remains valid. An uncertain earlier control returns control_pending until its ACK, after which the operator may retry. The control payload is {"mission_id": "mission UUID", "execute_command_id": "current execute command UUID"}; execute_command_id binds the request to that execute attempt.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Mission UUID
Responses
200
Cancel requested, or queued execute cancelled locally
Invalid mission status or control_pending while an uncertain prior control resolves
404
Mission not found
POST
/api/v1/operator/missions/:id/pause
Web Session
Pause mission
Description
Queues fleet.mission.pause with payload {"mission_id": "mission UUID", "execute_command_id": "current execute command UUID"}. execute_command_id binds the control to the current execute attempt. Returns current in_progress status with control_action/status/command_id; paused only after robot success ACK.
Queues fleet.mission.resume with payload {"mission_id": "mission UUID", "execute_command_id": "current execute command UUID"}. execute_command_id binds the control to the current execute attempt. Returns current paused status until robot success ACK.
/api/v1/operator/missions/:id/complete
Web Session
Complete mission
Description
Manual override only: closes an in-progress mission without proving device success. Requires JSON {"mission":{"reason":"operator-entered explanation"}}; records actor/reason in audit and execution_result, sets execution_status=overridden, and fences late device outcomes.
Invalid mission status or missing, blank, or non-string override reason (override_reason_required)
404
Mission not found
POST
/api/v1/operator/missions/:id/fail
Web Session
Fail mission
Description
Manual override only: closes an in-progress or paused mission without proving device failure. Requires JSON {"mission":{"reason":"operator-entered explanation"}}; records actor/reason in audit and execution_result and sets execution_status=overridden.
Invalid mission status or missing, blank, or non-string override reason (override_reason_required)
404
Mission not found
GET
/api/v1/operator/robots/:id/diagnostics
Web Session
List robot diagnostics
Description
Returns diagnostic metrics for a robot, with optional metric name, status, and time range filters. Newest first. Filters are accepted flat or as filter[...]; filter[limit] defaults to 100 and is clamped to 500 (a top-level limit is ignored).
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Robot UUID
metric_name
query
string
No
Filter by metric name (e.g. battery_health, motor_temp)
status
query
string
No
Filter by diagnostic status
since
query
datetime (ISO 8601)
No
ISO 8601 datetime lower bound for recorded_at
cohort_id
query
string
No
Restrict to entities whose robot belongs to this cohort, statically or by rule
tag
query
string
No
Restrict to entities whose robot carries this tag (lowercased and trimmed). Exactly one tag; a value that does not resolve to a single tag matches nothing rather than being ignored.
Returns fleet alerts for the workspace newest first, with optional filters for severity, status, robot, and alert type. Always bounded: `limit` defaults to 100 and is clamped to 500.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
severity
query
string
No
Filter by severity
status
query
string
No
Filter by alert status
robot_id
query
string
No
Filter by robot UUID
alert_type
query
string
No
Filter by alert type
cohort_id
query
string
No
Restrict to entities whose robot belongs to this cohort, statically or by rule
tag
query
string
No
Restrict to entities whose robot carries this tag (lowercased and trimmed). Exactly one tag; a value that does not resolve to a single tag matches nothing rather than being ignored.
limit
query
integer
No
Maximum fleet alerts to return, newest first (default 100, server maximum 500)
Returns OTA updates in the current workspace newest first. Filters are read only from the nested `filter` map — `?status=pending` is ignored. Use `filter[status]`, `filter[update_type]`, `filter[robot_id]`, and `filter[limit]`. Always bounded: `filter[limit]` defaults to 100 and is clamped to 500.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
filter[status]
query
string
No
Filter by OTA update status. Top-level `?status=` is ignored.
filter[update_type]
query
string
No
Filter by update type. Top-level `?update_type=` is ignored.
filter[robot_id]
query
string
No
Filter by robot UUID. Top-level `?robot_id=` is ignored.
filter[limit]
query
integer
No
Maximum OTA updates to return (default 100, server maximum 500). Top-level `?limit=` is ignored.
Compatibility endpoint. With a signed `artifact` object, creates a one-target device-confirmed rollout; without it creates an explicit `artifact_required` non-dispatchable legacy draft. The server never advances device phases itself. Use POST /ota-updates/rollouts for multi-target or cohort selection.
Snapshots 1-100 eligible robots from robot_ids or cohort_id in stable UUID order. Each attempt is unique; first-wave size is ceil(target_count * rollout_percentage / 100), and later waves require every current target's device-confirmed success. 0% remains staged without dispatch. Only deployment-allowlisted HTTPS hosts are accepted. Devices, not this server, verify bytes, SHA-256 and Ed25519 signature before install.
Auth
Web Session
Request Body
Field
Type
Required
Description
rollout
object
Yes
Rollout attributes; flat body also accepted
rollout.cohort_id
string
No
Workspace cohort UUID; at most 100 resolved robots
rollout.firmware_version
string
Yes
Desired version
rollout.rollout_percentage
integer
No
First-wave percentage 0-100, default 100
rollout.robot_ids
array<object>
No
Explicit robot UUIDs (1-100); exclusive with cohort_id
rollout.artifact
object
Yes
uri (allowlisted HTTPS, no query/userinfo/fragment), sha256 (64 hex), size_bytes (1-2147483648), signature {algorithm: ed25519, key_id, value: base64 64-byte signature}
Responses
201
Rollout created; OTAUpdate compatibility fields plus rollout summary
Returns: OTAUpdate
400
Invalid artifact, targets, percentage, workspace or overlapping OTA
422
Invalid update type or scheduled timestamp
GET
/api/v1/operator/ota-updates/:id/attempts
Web Session
Get OTA rollout attempts and progress
Description
Workspace-scoped attempt snapshot with total/completed/failed/staged counts, device phases, wave number, known prior version, command ID, bounded result and error. Artifact URI is never returned.
/api/v1/operator/ota-updates/:id/rollback
Web Session
Rollback OTA update
Description
Requests device-local rollback to the previous version captured at rollout creation. Optional attempt_id selects a workspace-owned install target of this update; omitted selects the legacy representative robot_id. Conflicting robot_id selectors are rejected. Returns 202 rollback_requested; rolled_back is recorded only after matching device ACK. Legacy rows without a known prior version are refused.
Auth
Web Session
Request Body
Field
Type
Required
Description
attempt_id
string
No
Install attempt UUID of a target in this OTA rollout; omit for legacy representative target
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
OTA update UUID
Responses
202
Rollback requested, awaiting device confirmation
400
Invalid status, missing previous version, or active newer OTA
404
OTA update not found
GET
/api/v1/operator/ota-updates/:id/status
Web Session
/api/v1/operator/missions/:id/waypoints
Web Session
Update mission waypoints
Description
Replaces the waypoints for a mission with the provided list. Each waypoint must be an object with a numeric lat (-90 to 90) and lng (-180 to 180), plus an optional action string and params object. A body that is not a list, or holds any other entry, is refused with 422 and the stored waypoints are left unchanged.
/api/v1/operator/missions/:id/auto-assign
Web Session
Auto-assign mission to robot
Description
Automatically assigns the best available online robot to a pending mission. Selection prefers higher battery and, when `payload.required_capabilities` is a non-empty array, robots whose capabilities overlap that list.
/api/v1/operator/missions/auto-assign-all
Web Session
Auto-assign all pending missions
Description
Automatically assigns pending missions to available online robots, most urgent first, and returns one result object per mission. Up to 500 pending missions are handled per call; call again to work through a longer backlog. Each result is `{status: "ok", mission_id, robot_id}` or `{status: "error", mission_id, reason}` — `reason` is `no_available_robot` once the free robots (or those with the required capabilities) have been handed out.
Auth
Web Session
Responses
200
Assignment results, one object per pending mission handled.
Creates a group or site cohort. Names are unique per workspace. Supplying `rules` makes membership dynamic: robots matching the rule appear as members at read time without membership rows.
Returns a cohort with a bounded page of its static membership rows. Rule-matched members are not membership rows and are not listed here — query the robots endpoint with `cohort_id` for the full membership.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Cohort UUID
limit
query
integer
No
Maximum membership rows to return (default 100, server maximum 500)
Deletes a cohort and its membership rows only when no geofence definition, including archived history, references it. Member robots and service agents are untouched; a referenced cohort returns a normal 422 instead of silently widening a safety rule.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Cohort UUID
Responses
204
Cohort deleted
404
Cohort not found
422
Cohort has geofences
POST
/api/v1/operator/cohorts/:cohort_id/members
Web Session
Add cohort member
Description
Adds a robot or a service agent to a cohort. Supply exactly one of `robot_id` / `service_agent_id`. Robots that have not been claimed yet can be added — a linked service agent is not required.
Applies one action to many robots and returns immediately; the work runs
on the `fleet` queue and progress is readable from the show endpoint or
the `fleet.batch_operation.*` realtime events.
Supply exactly one selection: `robot_ids` (an explicit list) or `filter`
(the same filter the fleet board uses). A `filter` selection is
**snapshotted at creation** — robots that start matching afterwards are
not swept in. Selections are capped at 5000 targets.
Decommissioned robots are never targeted, and a robot retired between
selection and execution is recorded as `skipped` rather than mutated.
`set_status` accepts only operator-settable statuses (online, offline,
charging, error, maintenance); `pending` and `decommissioned` belong to
the claim and decommission flows and are rejected.
Auth
Web Session
Request Body
Field
Type
Required
Description
reason
string
No
Why this was run. Recorded on the audit event (max 500 characters).
filter
object
No
Fleet-board filter to expand into targets: `query`, `status`, `robot_type`, `cohort_id`, `tag`. An empty map means every in-service robot. Any other key, or a value the fleet board would reject, returns 400 `invalid_filter`.
params
object
Yes
Action parameters: `status` for set_status; `tags` (array) for apply_tags/remove_tags; `cohort_id` for add_to_cohort/remove_from_cohort; `command_type` and optional `payload` for dispatch_command; none for acknowledge_alerts.
/api/v1/operator/batch_operations/:id/cancel
Web Session
Cancel a bulk operation
Description
Stops a queued or running batch. Cancellation is cooperative: targets already claimed by the running chunk finish, every target still queued becomes `skipped`, and the batch records who cancelled it. A batch that already finished returns 400.
Auth
Web Session
Request Body
Field
Type
Required
Description
reason
string
No
Optional cancellation reason, stored trimmed with credentials redacted, at most 500 characters
/api/v1/operator/batch_operations/:id/retry_failed
Web Session
Retry a bulk operation's failures
Description
Creates a child batch — same kind, same params, `parent_id` pointing at the original — carrying only the targets that failed. The original batch is left exactly as it was, so the record of the first attempt survives. Each failure is retried once from its batch: targets an earlier child already carries are left out, so repeating the call never re-dispatches to the same robots, and a failure that fails again is retried from that child. Returns 400 `no_failed_targets` when nothing failed and 400 `already_retried` when every failure already has a retry.
Creates a workspace-wide or cohort-scoped safety rule. At most 100 definitions may be active per workspace; inactive definitions do not count. GeoJSON-like Circle {type, center: [lng, lat], radius_m: 1..100000} or Polygon {type, coordinates: [[[lng, lat], ...]]}; one simple ring of 3..100 vertices, no holes or antimeridian crossing. Boundaries count as inside.
Auth
Web Session
Request Body
Field
Type
Required
Description
geofence
object
No
Definition; wrapper optional
geofence.active
boolean
No
Whether this rule evaluates, default true
geofence.name
string
Yes
2..120 character name
geofence.cohort_id
string
No
Optional same-workspace site or group UUID
geofence.breach_on
string (enter, exit)
No
Exit keeps robots inside (default); enter keeps them out
geofence.geometry
object
Yes
GeoJSON-like Circle {type, center: [lng, lat], radius_m: 1..100000} or Polygon {type, coordinates: [[[lng, lat], ...]]}; one simple ring of 3..100 vertices, no holes or antimeridian crossing. Boundaries count as inside.
GeoJSON-like Circle {type, center: [lng, lat], radius_m: 1..100000} or Polygon {type, coordinates: [[[lng, lat], ...]]}; one simple ring of 3..100 vertices, no holes or antimeridian crossing. Boundaries count as inside.
Edits bump the evaluation revision so stale robot boundary state cannot suppress a new breach. GeoJSON-like Circle {type, center: [lng, lat], radius_m: 1..100000} or Polygon {type, coordinates: [[[lng, lat], ...]]}; one simple ring of 3..100 vertices, no holes or antimeridian crossing. Boundaries count as inside.
Auth
Web Session
Request Body
Field
Type
Required
Description
geofence
object
No
Changed fields; wrapper optional
geofence.active
boolean
No
Whether this rule evaluates, default true
geofence.name
string
Yes
2..120 character name
geofence.cohort_id
string
No
Optional same-workspace site or group UUID
geofence.breach_on
string (enter, exit)
No
Exit keeps robots inside (default); enter keeps them out
geofence.geometry
object
Yes
GeoJSON-like Circle {type, center: [lng, lat], radius_m: 1..100000} or Polygon {type, coordinates: [[[lng, lat], ...]]}; one simple ring of 3..100 vertices, no holes or antimeridian crossing. Boundaries count as inside.
Archives the definition and preserves its boundary state, historical fleet alerts, location samples and committed event evidence. Open incidents are not automatically resolved: deletion does not certify a robot has returned to safety.
Auth
Web Session
Parameters
Name
In
Type
Required
Description
id
path
string
Yes
Geofence UUID
Responses
204
Deleted
404
Not found
GET
/api/v1/operator/robots/:id/locations
Web Session
Robot location history
Description
Trusted append-only fixes in observed_at DESC, id DESC order. Default limit 25, maximum 100; next_cursor is signed, versioned, workspace/robot-bound, and invalid/tampered cursors return 400. Coordinates use [longitude, latitude]. The response includes current trusted position and at most 100 active per-fence states with numeric transition_version (Unix microseconds), safety_state safe|breached|unknown and geometry/state revisions. Missing state has not been evaluated; unknown means an edited fence awaits a trusted fix. On reconnect hydrate this snapshot first. Key state by workspace+robot+fence; treat its transition_version as an inclusive floor and ignore any event version less than or equal to the snapshot version, even with an unseen event id. Thereafter accept only strictly larger versions and reject older geometry_revision values. Stable envelope ids permit duplicate detection. Realtime/webhook delivery order is not guaranteed.
/api/v1/operator/maintenance-work-orders
Web Session
List maintenance work orders
Description
Workspace-scoped newest-first keyset page, 25 by default and at most 100. Return next_cursor to continue; no records are silently hidden. Filter by status, priority, robot_id, alert_id, assignee_user_id, due_before, or due_after.
/api/v1/operator/maintenance-work-orders
Web Session
Create maintenance work order
Description
Operator-only. Link an optional same-robot fleet alert and/or a current warning/critical prediction_category; the latter is snapshotted at creation. Assignee must have an active workspace membership. due_at is ISO8601 UTC and may be in the past for overdue backlog. Only in_progress reserves a robot. Beginning work atomically refuses active missions and reserved OTA attempts; open/scheduled are planning. Completion/cancellation releases the work-order hold but preserves a separate manual hold. Heartbeats cannot clear either hold. A linked robot is restored online only after a heartbeat within 300 seconds; otherwise it remains offline (or pending if unlinked). No prediction or page read creates an order.
/api/v1/operator/maintenance-work-orders/:id
Web Session
Assign, schedule, begin, or cancel work
Description
Operator-only compare-and-set transition. Allowed: open→scheduled/in_progress/cancelled, scheduled→open/in_progress/cancelled, in_progress→cancelled. Terminal records are immutable; duplicate terminal same-result action is idempotent. Only in_progress reserves a robot. Beginning work atomically refuses active missions and reserved OTA attempts; open/scheduled are planning. Completion/cancellation releases the work-order hold but preserves a separate manual hold. Heartbeats cannot clear either hold. A linked robot is restored online only after a heartbeat within 300 seconds; otherwise it remains offline (or pending if unlinked). No prediction or page read creates an order.
/api/v1/operator/maintenance-work-orders/:id/complete
Web Session
Complete maintenance work order
Description
Operator-only; in_progress required. Outcome is mandatory, completion time and actor are server recorded. Parts/notes/time are durable; an identical retry is idempotent. Only in_progress reserves a robot. Beginning work atomically refuses active missions and reserved OTA attempts; open/scheduled are planning. Completion/cancellation releases the work-order hold but preserves a separate manual hold. Heartbeats cannot clear either hold. A linked robot is restored online only after a heartbeat within 300 seconds; otherwise it remains offline (or pending if unlinked). No prediction or page read creates an order.
/api/v1/operator/maintenance-work-orders/:id/events
Web Session
Read work-order lifecycle evidence
Description
Recent detailed lifecycle events, newest first and bounded to 25 by default or at most 100. Detail rows become eligible only when both the terminal order's updated_at and the event's inserted_at are older than the workspace's work_order_evidence_retention_days (400 days by default); active-order detail never expires. The response includes archived_audit_url, a workspace-scoped paginated audit link filtered to this work order. The immutable audit counterpart preserves each original event ID, actor, action, statuses, reason, priority, assignee and event time. The work-order outcome, parts and alert/prediction links remain on the summary. Do not infer that an empty recent-events list means no historical transitions.
/api/v1/operator/maintenance-work-orders/:id/audit-events
Web Session
Page immutable work-order lifecycle audit
Description
Workspace/order-bound keyset history ordered by original event time and original work_order_event_id, not the coarse audit insertion time. Default 25, maximum 100. Pass next_cursor unchanged; the signed cursor binds workspace and work order, and a missing/deleted anchor or foreign/tampered cursor returns 400. The response retains actor, action, from/to status, reason, priority/assignee, available robot/alert references, exact event_occurred_at and archival provenance after terminal detail rows expire. reference_provenance maps robot_id and alert_id independently to event_evidence or not_recorded; an explicit historical null still counts as event evidence. Missing refs are never inferred from today's mutable order. Unrecognized legacy evidence keys/types fail closed and leave the detail row for review rather than copying arbitrary data into audit. Audit history has no automatic time purge; explicit data deletion can remove it.