Log in Create workspace
Skip to content

API reference

Fleet

Let an enrolled robot report its own heartbeat, diagnostics, telemetry backlog, and OTA outcome.

Jump to 7 endpoints
On this page

Overview

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

bash
export AGENTOPS_BASE_URL="https://robotscenter.net"
export ROBOT_TOKEN="machine_token_with_fleet_write"
export TELEMETRY_BATCH_ID="robot-17-2026-09-16T12:00:00Z"

jq -n --arg batch_id "$TELEMETRY_BATCH_ID" \
  '{batch_id: $batch_id, readings: [{metric_name: "battery.voltage", metric_value: 48.6, unit: "V", status: "normal", recorded_at: "2026-09-16T11:59:55Z"}, {metric_name: "motor.temperature", metric_value: 61.2, unit: "C", status: "warning", recorded_at: "2026-09-16T11:59:58Z"}]}' |
  curl --fail-with-body --silent --show-error \
    -X POST -H 'content-type: application/json' \
    -H "Authorization: Bearer $ROBOT_TOKEN" \
    --data-binary @- "$AGENTOPS_BASE_URL/api/v1/robots/me/telemetry" | jq .
What to expect

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.

Auth

API key / access token

Required Scopes

fleet:read
Responses
200

Authenticated robot

Returns: Robot

Response Body
Field Type Required Description
id string Yes Robot UUID
name string Yes Robot name
status string (pending, online, offline, charging, error, maintenance, decommissioned) Yes 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
Example
{
  "battery_level": 82,
  "capabilities": [
    "navigate",
    "pickup",
    "delivery"
  ],
  "firmware_version": "3.2.1",
  "id": "d2c9a4df-c6ad-4016-9ba4-30c1c6d53a22",
  "inserted_at": "2026-03-25T08:00:00Z",
  "last_seen_at": "2026-03-27T09:12:10Z",
  "location": {
    "lat": 41.717,
    "lng": 44.793,
    "zone": "A3"
  },
  "manufacturer": "RoboWorks",
  "metadata": {
    "shift": "day"
  },
  "model": "AX-7",
  "name": "Aisle Runner 7",
  "robot_type": "amr",
  "serial_number": "AMR-AX7-0042",
  "service_agent_id": "f4d49601-3477-4c9d-b896-ac08a26a3147",
  "status": "online",
  "tags": [
    "night-shift",
    "warehouse-b"
  ],
  "updated_at": "2026-03-27T09:12:10Z",
  "workspace_id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea"
}
401

Invalid or missing robot credential

403

fleet:read scope required

404

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.
Responses
200

Robot heartbeat recorded

Returns: Robot

Response Body
Field Type Required Description
id string Yes Robot UUID
name string Yes Robot name
status string (pending, online, offline, charging, error, maintenance, decommissioned) Yes 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.
Responses
201

Diagnostic recorded

Returns: Diagnostic

Response Body
Field Type Required Description
id string Yes Diagnostic UUID
status string (normal, warning, critical) Yes Diagnostic status
unit string No Metric unit
metadata object Yes Diagnostic metadata
inserted_at datetime (ISO 8601) Yes Record insertion timestamp
updated_at datetime (ISO 8601) Yes Record update timestamp
workspace_id string Yes Workspace UUID
robot_id string Yes Robot UUID
metric_name string Yes Metric name
metric_value number Yes Metric numeric value
recorded_at datetime (ISO 8601) Yes When the metric was recorded
Example
{
  "id": "b3ec0b4e-64be-44f3-a5f6-e6f54c8bf190",
  "inserted_at": "2026-03-27T09:11:30Z",
  "metadata": {},
  "metric_name": "battery_health",
  "metric_value": 76.5,
  "recorded_at": "2026-03-27T09:11:30Z",
  "robot_id": "d2c9a4df-c6ad-4016-9ba4-30c1c6d53a22",
  "status": "warning",
  "unit": "percent",
  "updated_at": "2026-03-27T09:11:30Z",
  "workspace_id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea"
}
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

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
batch_id string No Batch id, generated when not supplied
robot_id string No Robot the batch was recorded for
Example
{
  "duplicate": false,
  "accepted": 2,
  "errors": [],
  "batch_id": "b-2026-08-03T11:00:00Z-001",
  "robot_id": "0f9d1c2e-3b4a-4c5d-8e6f-7a8b9c0d1e2f"
}
400

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.
acknowledgement.status string (downloading, installing, completed, failed, rolled_back) Yes Monotonic device-reported result
acknowledgement.error_message string No Failure detail; persisted only when status is failed
acknowledgement.command_id string Yes Correlated command UUID
acknowledgement.attempt_id string Yes Rollout attempt UUID
acknowledgement.verified_version string No Required for completed/rolled_back; must equal desired version
Parameters
Name In Type Required Description
id path string Yes OTA update UUID
Responses
200

OTA result recorded

Returns: OTAUpdate

Response Body
Field Type Required Description
id string Yes OTA update UUID
status string (pending, downloading, installing, completed, failed, rolled_back) Yes OTA lifecycle status
metadata object Yes OTA metadata payload
update_type string (firmware, software, config, security_patch) Yes OTA update type
error_message string No Failure detail
inserted_at datetime (ISO 8601) Yes Record insertion timestamp
updated_at datetime (ISO 8601) Yes Record update timestamp
completed_at datetime (ISO 8601) No Completion timestamp
scheduled_at datetime (ISO 8601) No Scheduled timestamp
workspace_id string Yes Workspace UUID
robot_id string No Robot UUID
delivery_state string Yes awaiting_device for a signed rollout; artifact_required for legacy drafts
firmware_version string Yes Target version
rollback_version string No Device-confirmed version restored by rollback
rollout object No Rollout summary; null for artifact-less legacy rows
rollout_percentage integer Yes Rollout percentage
Example
{
  "completed_at": null,
  "delivery_state": "artifact_required",
  "error_message": null,
  "firmware_version": "3.3.0",
  "id": "5ed7cc57-e9a0-44b9-b345-8d8cc6ddf1aa",
  "inserted_at": "2026-03-27T09:12:00Z",
  "metadata": {},
  "robot_id": "d2c9a4df-c6ad-4016-9ba4-30c1c6d53a22",
  "rollback_version": null,
  "rollout": null,
  "rollout_percentage": 100,
  "scheduled_at": "2026-03-27T09:25:00Z",
  "status": "pending",
  "update_type": "firmware",
  "updated_at": "2026-03-27T09:14:00Z",
  "workspace_id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea"
}
400

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.

Auth

API key / access token

Required Scopes

fleet:write
Request Body
Field Type Required Description
acknowledgement object Yes ACK body; flat body also accepted
acknowledgement.status string (downloading, installing, completed, failed, rolled_back) Yes Device phase
acknowledgement.result object No Device result object up to 4096 JSON bytes
acknowledgement.error_message string No Failure detail, max 2000 characters
acknowledgement.command_id string Yes Matching command UUID
acknowledgement.verified_version string No Required on completed or rolled_back
Parameters
Name In Type Required Description
id path string Yes OTA attempt UUID
Responses
200

Attempt status recorded

400

Invalid or stale transition/command/version

401

Invalid robot credential

403

fleet:write required

404

Attempt not found for this robot

GET
/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.

Auth

API key / access token

Required Scopes

fleet:read
Parameters
Name In Type Required Description
limit query string No Page size, 1..100
cursor query string No Opaque signed next_cursor from prior page
Responses
200

Own location page

Example
{
  "current": {
    "coordinates": [
      44.8,
      41.7
    ],
    "location": {
      "lat": 41.7,
      "lng": 44.8
    },
    "observed_at": "2026-09-25T12:00:00.000000Z"
  },
  "geofence_states": [
    {
      "alert_id": null,
      "geofence_id": "6a05ee70-faa4-40f6-8f14-b49465c2d398",
      "geometry_revision": 1,
      "observed_at": "2026-09-25T12:00:00.000000Z",
      "safety_state": "safe",
      "state_revision": 1,
      "transition_version": 1790337600000000
    }
  ],
  "next_cursor": null,
  "samples": [
    {
      "coordinates": [
        44.8,
        41.7
      ],
      "id": "e8f77d1f-19af-4ded-91ab-b346058e7c31",
      "location": {
        "lat": 41.7,
        "lng": 44.8
      },
      "observed_at": "2026-09-25T12:00:00.000000Z",
      "received_at": "2026-09-25T12:00:01.000000Z",
      "robot_id": "6a05ee70-faa4-40f6-8f14-b49465c2d398"
    }
  ]
}
403

fleet:read scope required

Where to go next

see also