List a run's evidence
Every piece of evidence posted against a run's steps — confirmations, scans, notes, instrument states and measurements — oldest first, a page at a time.
https://app.xplantpro.com/ api/ v1/ sop-runs/ {id}/ eventsread:sop_runsGet an SOP run returns the first 50 with the run, and meta.events_next_cursor for the rest: pass it here as cursor. Or start here without a cursor to walk the whole trail. A cursor from another run's trail is refused. A page past the end of the trail is empty, with meta.next_cursor null, rather than an error.
See also: Run an SOP from a bench station.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | The run's id. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Page size. Values above 200 are capped at 200. Default 50. From 1 to 200. |
offset | integer | No | Number of records to skip. Prefer cursor where a list offers it: an offset shifts when records are added ahead of it. Default 0. At least 0. |
cursor | string | No | Continue from the previous page: pass its meta.next_cursor unchanged, with the same filters. Treat it as opaque. Not combinable with offset. Up to 2048 characters. |
Example
curl "https://app.xplantpro.com/api/v1/sop-runs/2a4c6e8b-1d3f-4b5a-a7c9-e1f3a5b7c9d1/events?limit=50" \
-H "Authorization: Bearer $XPLANT_API_KEY"Response
200 with { "ok": true, "data": … }. data is an array. To get the next page, pass meta.next_cursor back as cursor; it is null on the last page. See Pagination.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | — |
runId | string (uuid) | Yes | The run the evidence belongs to. |
stepKey | string | Yes | The step it was recorded against — the stepId it was posted to. |
eventType | string | Yes | confirmed, scanned, skipped, note or device_state for a step event; measured for a measurement. |
recordedAt | string | Yes | When it happened: the recorded_at that was sent, or when xPlant received it. |
payload | object | Yes | The detail sent with the event. For a measurement: metric, value, unit, and notes when there were any. |
{
"ok": true,
"data": [
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"runId": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"stepKey": "step-2",
"eventType": "measured",
"recordedAt": "2026-09-25T08:21:40+00:00",
"payload": {
"metric": "ph",
"value": 5.7,
"unit": "pH",
"notes": "Initiation medium before pouring."
}
},
{
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"runId": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"stepKey": "step-3",
"eventType": "scanned",
"recordedAt": "2026-09-25T08:30:00+00:00",
"payload": {
"code": "LINE-0412-J07"
}
}
]
}The envelope can also carry meta:
| Field | Type | Required | Description |
|---|---|---|---|
next_cursor | string | null | Yes | Pass as cursor to fetch the next page. null means this is the last page. |
| Response header | Meaning |
|---|---|
X-RateLimit-Limit | The request budget this answer reports on, per minute: your key's or device token's (1,000) or your workspace's (3,000), whichever has fewer requests left. Left out when the budget could not be read. |
X-RateLimit-Remaining | Requests left in that budget this minute, after this one. 0 on a 429. A workspace's other keys spend its budget too, so this can fall faster than your own requests alone would. |
X-RateLimit-Reset | Whole seconds until that budget's minute ends and it is full again. Never more than 60. |
X-Request-Id | Identifies this request. Include it when you contact support. |
Errors
| Status | Code | When |
|---|---|---|
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's plan does not include the API. The full API is included with xPlant+ Teams and Enterprise; on Hobby and Pro Lab, keys can connect devices only. |
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 | No run with this id exists in the key's workspace, or the run id is not a well-formed id. |
422 | VALIDATION_ERROR | Both cursor and offset were sent; use one. |
422 | INVALID_CURSOR | The cursor is malformed, or came from a different list or different filters. Start again without it. |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | SOP_RUN_QUERY_FAILED | The run's evidence could not be read. Retry later. |
Branch on code, never on the error text. See Errors.
Complete an SOP run
Ends a run: completed when the procedure was carried out, failed when it was carried out and did not work, cancelled when it was stopped before the end. Afterwards the run reads exactly as one closed in xPlant: its status is the outcome, and completedAt is the time of this call for completed and empty otherwise. A run marked failed raises the same deviation alert it raises in xPlant.
Record step evidence
Records what happened at one step of a run: someone confirmed it, scanned a label or vessel, skipped it, or left a note, or an instrument reported its state.