These endpoints are for software running on a robot: report that it is online, upload measurements, and report update progress to the operator. Each enrolled robot acts as itself; the server finds its robot record from the credential, so you never choose another robot's ID. Use the separate operator fleet API to manage a whole fleet.
Identity selects the robot
GET /api/v1/robots/me and every write resolve the robot from the credential. A service agent not linked to a robot receives 404; a caller cannot redirect a heartbeat or telemetry batch with a request field.
Telemetry supports bounded backfill
One batch contains 1 through 500 readings. recorded_at may be seven days old or up to five minutes ahead, enabling offline drain with modest clock skew; valid readings can be accepted while invalid indices are reported in errors.
A batch ID is consumed once
Replaying an accepted batch_id returns duplicate: true and writes nothing even when the new body differs. A partially valid first batch also consumes the ID, so correct rejected readings under a new batch ID rather than retrying changed content.
Trusted position is not a telemetry backfill
POST /api/v1/robots/me/heartbeat accepts a numeric lat/lng location only when observed_at is at most five minutes old or 30 seconds ahead and newer than the previous trusted fix. It appends a sample and evaluates active geofences atomically. GET /api/v1/robots/me/locations needs fleet:read and pages recent fixes with a signed cursor (25 default, 100 max) while returning current trusted position and active boundary state separately. The seven-day telemetry batch backlog does not apply to heartbeat location.
OTA success is device evidence
For a signed artifact rollout, consume the fleet.ota.install or fleet.ota.rollback command and keep its command_id. The device must fetch and verify actual bytes against SHA-256 and Ed25519 metadata before installing or reporting completed. POST /api/v1/robots/me/ota-attempts/:id/acknowledge with matching command_id, monotonic status and verified_version on completed/rolled_back. The server validates metadata and correlation; it does not fetch or verify bytes on the device's behalf. A legacy single-target OTA row may be partial and is not a signed rollout.
Before you begin
The command-line examples require curl and jq; any exception is identified in its introduction.
A robot enrollment claimed by the device so its service-agent identity is linked to a robot record.
An API key or access token with fleet:read and/or fleet:write as needed.
A durable device-side batch ID generator for retry-safe offline telemetry upload.
How it works
Confirm the linked unit
Read /api/v1/robots/me after enrollment and retain its robot ID only for logs and reconciliation; do not treat it as an input to self-service writes.
Publish liveness separately
Send heartbeats with battery, location, firmware, or metadata. The server sets status online and updates last-seen time; any client-supplied status is ignored.
Rehydrate safety on reconnect
Read /api/v1/robots/me/locations for current fix and geofence_states, then accept geofence.breach/clear events only with a strictly larger transition_version for the same fence and a non-older geometry_revision. Event IDs are stable for retries; realtime delivery may repeat or arrive out of order.
Drain measurements in stable batches
Group readings into at most 500, assign a unique batch ID, and inspect both accepted and per-index errors. Accepted readings count against the monthly telemetry-points quota.
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.
Upload an idempotent telemetry batch
The batch contains two complete readings and can be retried unchanged after an ambiguous network failure.
HTTP 200 returns robot_id, the batch ID, accepted count, duplicate flag, and an errors array. It deliberately does not echo all readings.
Troubleshooting
Self lookup returns 404
The authenticated service agent is not linked to a robot, or the linked robot no longer exists. Claim a robot enrollment rather than using a general service-agent credential.
Telemetry returns 200 with errors
The batch was partially valid. Keep accepted points as committed, correct only the rejected indices, and send those corrections under a fresh batch ID.
The same device sees 401 or 410
Normal decommissioning revokes the identity and usually produces 401. A surviving valid credential against a decommissioned robot is refused with 410 robot-decommissioned; neither response is retryable device work.
Telemetry returns 429
Distinguish the per-credential request limit from the workspace's telemetry_points_per_month quota in the problem body, then honor Retry-After or resolve the plan limit.
OTA command was accepted but no terminal result appears
Do not infer completion or blindly reinstall. Inspect the bound attempt command_id and local version, then send the matching attempt-scoped ACK. A transport interruption after acceptance is outcome unknown, not a server-certified failure.
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/robots/me
API key / access token
Get authenticated robot
fleet:read
Description
Returns the robot linked to the authenticated service-agent identity. The robot ID is derived from the credential and cannot be supplied by the caller.
Current robot status. `pending` is a pre-registered unit with no identity yet; `decommissioned` is a retired unit, kept for history but excluded from fleet listings and from the plan's robot quota.
Authenticated service agent is not linked to a robot
POST
/api/v1/robots/me/heartbeat
API key / access token
Record authenticated robot heartbeat
fleet:write
Description
Records a heartbeat for the robot owned by the authenticated service-agent identity. Only `battery_level` (0-100), validated `location`, `firmware_version`, and `metadata` are accepted. A location requires numeric lat/lng; optional observed_at is ISO8601, no more than five minutes old or 30 seconds future, and strictly newer than the prior trusted fix. A valid fix atomically appends history and evaluates active geofences; omitted location preserves the current position. `status` is forced to `online` and `last_seen_at` is set by the server — a client-supplied status is ignored. The body may be wrapped in `heartbeat` or sent flat.
Auth
API key / access token
Required Scopes
fleet:write
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 ISO8601 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.
Current robot status. `pending` is a pre-registered unit with no identity yet; `decommissioned` is a retired unit, kept for history but excluded from fleet listings and from the plan's robot quota.
metadata
object
Yes
Arbitrary metadata payload
location
object
Yes
Robot location payload
inserted_at
datetime (ISO 8601)
Yes
Record insertion timestamp
updated_at
datetime (ISO 8601)
Yes
Record update timestamp
tags
array<string>
Yes
Free-form tags, lowercased and trimmed
service_agent_id
string
No
Service agent UUID
workspace_id
string
Yes
Workspace UUID
capabilities
array<string>
Yes
Declared robot capabilities
last_seen_at
datetime (ISO 8601)
No
Last heartbeat timestamp
firmware_version
string
No
Current firmware version
serial_number
string
Yes
Unique serial number
battery_level
integer
Yes
Battery percentage (0-100)
manufacturer
string
No
Manufacturer name
model
string
No
Model name
robot_type
string (amr, drone, manipulator, humanoid)
Yes
Robot type
401
Invalid or missing robot credential
403
fleet:write scope required
404
Linked robot not found
410
The robot row is `decommissioned` but this credential is still valid (belt-and-braces). A normal decommission revokes the identity first, so the same write returns 401 instead. Type is https://robotscenter.net/problems/robot-decommissioned.
422
Validation error (invalid battery_level or other changeset errors)
POST
/api/v1/robots/me/diagnostics
API key / access token
Record authenticated robot diagnostic
fleet:write
Description
Records a single diagnostic metric for the robot owned by the authenticated service-agent identity. This write is not counted against `telemetry_points_per_month`; it is bounded only by per-credential rate limiting. The body may be wrapped in `diagnostic` or sent flat. `recorded_at` defaults to now and must be at most 7 days in the past and 5 minutes in the future (otherwise 422), the same window as batch telemetry.
Auth
API key / access token
Required Scopes
fleet:write
Request Body
Field
Type
Required
Description
diagnostic
object
No
Optional wrapper. The same fields are accepted at the top level when the wrapper is omitted.
diagnostic.status
string (normal, warning, critical)
No
Diagnostic status, defaults to normal
diagnostic.unit
string
No
Metric unit
diagnostic.metadata
object
No
Sensor metadata
diagnostic.metric_name
string
Yes
Metric name (free-form, max 120 characters)
diagnostic.metric_value
number
Yes
Numeric metric value
diagnostic.recorded_at
datetime (ISO 8601)
No
When the metric was recorded (defaults to now). At most 7 days in the past and 5 minutes in the future; otherwise 422.
The robot row is `decommissioned` but this credential is still valid (belt-and-braces). A normal decommission revokes the identity first, so the same write returns 401 instead. Type is https://robotscenter.net/problems/robot-decommissioned.
422
Validation error
POST
/api/v1/robots/me/telemetry
API key / access token
Ingest a telemetry batch
fleet:write
Description
Records up to 500 metric readings for the robot owned by the authenticated service-agent identity, in one transaction. Readings become ordinary diagnostics, so they show up everywhere single readings do.
Idempotency: supply a `batch_id` that is unique per payload. Replaying a `batch_id` already accepted for this robot returns 200 with `accepted: 0` and `duplicate: true` and writes nothing — this holds even if the payload differs, so never reuse a batch id for new readings. Omitting `batch_id` is allowed (the server generates one) but then the request carries no idempotency guarantee.
Backfill: `recorded_at` may be up to 7 days in the past, so an offline robot can drain its backlog on reconnect, and at most 5 minutes in the future to absorb clock skew. Readings outside that window are rejected individually.
Partial validity: malformed readings are reported per index in `errors` while the valid ones are accepted, and the batch is still consumed for idempotency — a retry of a partially bad batch will not re-insert its good half.
Metering: accepted readings count against the workspace's `telemetry_points_per_month` quota, and the request is also subject to per-credential rate limiting.
Auth
API key / access token
Required Scopes
fleet:write
Request Body
Field
Type
Required
Description
batch_id
string
No
Client-supplied id, unique per payload, at most 200 characters. Replays of an accepted id are acknowledged without writing. A non-string or over-long value is 400 `invalid_batch_id`.
readings
array<object>
Yes
Up to 500 readings. An empty list or a larger batch is rejected outright.
readings[].status
string (normal, warning, critical)
No
Diagnostic status, defaults to normal
readings[].unit
string
No
Metric unit, at most 40 bytes
readings[].metadata
object
No
Sensor metadata
readings[].metric_name
string
Yes
Metric name (free-form, max 120 bytes)
readings[].metric_value
number
Yes
Metric value
readings[].recorded_at
string
No
ISO 8601 timestamp the reading was taken, defaults to receipt time
Responses
200
Batch acknowledged
Response Body
Field
Type
Required
Description
duplicate
boolean
No
True when this batch id was already accepted; nothing was written
accepted
integer
No
Readings written
errors
array<object>
No
Per-index validation failures for rejected readings
The batch itself is unusable: empty (`empty_batch`), larger than 500 readings (`batch_too_large`), `readings` is not a list (`invalid_readings`), or `batch_id` is not a string / exceeds 200 characters (`invalid_batch_id`)
401
Invalid or missing robot credential
403
fleet:write scope required
404
Linked robot not found
410
The robot row is `decommissioned` but this credential is still valid (belt-and-braces). A normal decommission revokes the identity first, so the same write returns 401 instead. Type is https://robotscenter.net/problems/robot-decommissioned.
429
The workspace exceeded its monthly telemetry-points quota, or the credential exceeded its rate limit
POST
/api/v1/robots/me/ota-updates/:id/acknowledge
API key / access token
Acknowledge a rollout attempt via the compatibility update URL
fleet:write
Description
Requires attempt_id, command_id, status and verified_version for success. The authenticated robot, update and attempt must match exactly. Artifact-less legacy rows are non-dispatchable and cannot ACK. The body may be wrapped in acknowledgement or sent flat.
Auth
API key / access token
Required Scopes
fleet:write
Request Body
Field
Type
Required
Description
acknowledgement
object
No
Optional wrapper. The same fields are accepted at the top level when omitted.
Invalid status, stale command or mismatched verified version
401
Invalid or missing robot credential
403
fleet:write scope required
404
Update not found for the authenticated robot
POST
/api/v1/robots/me/ota-attempts/:id/acknowledge
API key / access token
Acknowledge device OTA attempt
fleet:write
Description
Preferred attempt-scoped ACK. Requires the authenticated robot identity, matching command_id and monotonic status downloading/installing/completed/failed/rolled_back. completed/rolled_back requires verified_version exactly matching the desired version; duplicate terminal ACK is idempotent, conflicting or stale ACK refused. The device must verify actual artifact bytes/digest/signature before success. This server validates metadata and never fetches the bytes.
/api/v1/robots/me/locations
API key / access token
Authenticated robot location history
fleet:read
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. The robot ID is derived from the fleet:read credential; no other robot can be requested.