Register a device
Adds a sensor, controller or gateway to the workspace so it can send heartbeats, readings and events. Then mint a device token for it and put that token on the device, rather than a workspace key.
https://app.xplantpro.com/ api/ v1/ deviceswrite:devicesHonours Idempotency-KeyIf the workspace has already connected every device its plan includes, or its plan includes none, the request is refused with DEVICE_LIMIT_REACHED. A retired device does not count. Send an Idempotency-Key so a retried request cannot register the same device twice.
Send an Idempotency-Key header to make retries safe: a repeat with the same key within 24 hours returns the first response instead of writing twice. See Idempotency.
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | No | Any unique string you choose per logical write. A retry carrying the same key within 24 hours returns the first result instead of writing again. Scoped to your API key and this operation. 8–255 characters. Must match ^[A-Za-z0-9._:~-]+$. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | What the lab calls the device, as it appears in the device list. 1–100 characters. |
type | "sensor" | "controller" | "gateway" | No | What kind of device this is. Default "sensor". |
hardware | string | No | The board or model, for example Raspberry Pi 4. Up to 100 characters. |
firmware_version | string | No | The firmware or software version the device runs. Up to 50 characters. |
room_id | string (uuid) | No | The growing room the device sits in. Must be a room in the workspace. |
metadata | object | No | Any JSON object you want kept with the device. Default {}. |
Example
curl -X POST https://app.xplantpro.com/api/v1/devices \
-H "Authorization: Bearer $XPLANT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Shelf 3 climate probe",
"type": "sensor",
"hardware": "Raspberry Pi 4",
"firmware_version": "1.4.2",
"room_id": "2c6f9a41-7d3e-4b8a-a1c5-9e0d4f7b3a26",
"metadata": {
"shelf": 3
}
}'Response
201 with { "ok": true, "data": … }. data holds the result.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | — |
name | string | Yes | — |
type | string | Yes | One of sensor, controller, gateway. |
hardware | string | null | Yes | — |
status | string | Yes | active while the device is in service. paused or retired once someone in the lab takes it out of service, which refuses its readings and revokes its device tokens. |
firmware_version | string | null | Yes | — |
room_id | string | null (uuid) | Yes | The growing room the device sits in, if one was set. |
last_seen_at | string | null | Yes | When the device was last heard from, as an ISO 8601 timestamp. A heartbeat updates it. Null if it has never been heard from. |
metadata | object | Yes | The JSON object stored with the device when it was registered. |
created_at | string | Yes | — |
updated_at | string | Yes | — |
{
"ok": true,
"data": {
"id": "8e3b1f52-6c0d-4a7e-9b21-5f4d8c2a7e13",
"name": "Shelf 3 climate probe",
"type": "sensor",
"hardware": "Raspberry Pi 4",
"status": "active",
"firmware_version": "1.4.2",
"room_id": "2c6f9a41-7d3e-4b8a-a1c5-9e0d4f7b3a26",
"last_seen_at": null,
"metadata": {
"shelf": 3
},
"created_at": "2026-09-01T09:30:00.000Z",
"updated_at": "2026-09-01T09:30:00.000Z"
}
}| Response header | Meaning |
|---|---|
Idempotent-Replay | true when this response is a replay of an earlier request with the same Idempotency-Key. |
X-Request-Id | Identifies this request. Include it when you contact support. |
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | The request body is not valid JSON. |
401 | UNAUTHORIZED | The key is missing, malformed or revoked, or its owner is no longer a member of the workspace. |
402 | PAID_PLAN_REQUIRED | The workspace has no paid plan. Connecting devices is included with every paid plan. |
402 | DEVICE_LIMIT_REACHED | The workspace has connected every device its plan includes — retire a device it no longer uses, or contact support to raise the limit — or its plan includes no connected devices at all. error says which. Nothing was registered. |
403 | FORBIDDEN | The key lacks a scope this operation requires, or its owner's current role in the workspace cannot use it — a key never does more than its owner can in xPlant. error names the scope, and for a role, the role it needs. |
403 | DEVICE_TOKEN_NOT_ACCEPTED | A device token was sent; this operation needs a workspace API key. |
404 | NOT_FOUND | room_id does not name a growing room in the workspace. Nothing was registered. |
409 | IDEMPOTENCY_IN_FLIGHT | A request with this Idempotency-Key is still being processed; retry after Retry-After seconds. |
422 | VALIDATION_ERROR | The Idempotency-Key header is malformed. |
422 | VALIDATION_ERROR | A field failed validation. error names the first one, for example name: String must contain at least 1 character(s). |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | DEVICE_CREATE_FAILED | The device could not be saved. Retry with the same Idempotency-Key. |
503 | DEVICE_LIMIT_UNAVAILABLE | The workspace's device allowance could not be checked, so nothing was registered. Retry shortly. |
Branch on code, never on the error text. See Errors.
List devices
Every device registered to the workspace, newest first, a page at a time. Pages hold up to 200 devices by default, so most workspaces receive every device in the first answer; follow meta.next_cursor until it comes back null to be sure you have them all.
Send a heartbeat
Tells xPlant the device is alive and stamps its last-seen time. xPlant shows a device as offline once 15 minutes pass without hearing from it, so send a heartbeat every minute or two. The request has no body.