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.
https://app.xplantpro.com/ api/ v1/ sop-runs/ {id}/ completewrite:sop_runsHonours Idempotency-KeyA run is closed by the person who started it, or by a manager or above in the lab, as in xPlant: the key's owner must be one of them. Once a run has ended it takes no more evidence and cannot be closed again.
Send an Idempotency-Key so a station retrying on a patchy connection gets the first answer back rather than a refusal. The key is matched together with the run and the body: the same key sent with a different outcome, different notes or for another run is not a retry, and is answered on its own — on a run that has already ended, with 409 SOP_RUN_CLOSED.
See also: Run an SOP from a bench station.
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.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | The run's id. |
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 |
|---|---|---|---|
outcome | "completed" | "failed" | "cancelled" | Yes | How the run ended: 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. |
notes | string | No | A closing remark, such as why a run was cancelled. It is added to the run's notes in xPlant, after anything already there; nothing already written is replaced. Up to 1000 characters. |
Example
curl -X POST https://app.xplantpro.com/api/v1/sop-runs/2a4c6e8b-1d3f-4b5a-a7c9-e1f3a5b7c9d1/complete \
-H "Authorization: Bearer $XPLANT_API_KEY" \
-H "Idempotency-Key: station-3-wk38-complete" \
-H "Content-Type: application/json" \
-d '{
"outcome": "completed",
"notes": "All vessels labelled and moved to shelf 3."
}'Response
200 with { "ok": true, "data": … }. data holds the result.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | — |
sopId | string | null (uuid) | Yes | The SOP being run. |
version | integer | null | Yes | The version of the SOP this run follows — the one in force when it started. It never changes afterwards. |
versionId | string | null (uuid) | Yes | That version's id. |
status | string | Yes | in_progress from the moment a run starts. A run ends as completed, failed or cancelled, closed in xPlant or with Complete an SOP run; runs can also be pending, on_hold or archived. |
batchCode | string | null | Yes | Your lab's own identifier for what is being run. |
startedAt | string | null | Yes | — |
completedAt | string | null | Yes | When the run was completed. Empty while it runs, and for a run that ended failed or cancelled. |
progress | number | Yes | How far through its steps the run is, from 0 to 100, as ticked off in xPlant. |
steps | any[] | Yes | The step checklist as ticked off in xPlant, one entry per step, such as { "id": "step-3", "completed": true }. Empty until someone works the run in xPlant. Evidence posted through the API is in events and does not tick steps off here. |
{
"ok": true,
"data": {
"id": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"sopId": "2e4f6a8c-0b1d-4e3f-9a5b-7c9d1e3f5a7b",
"version": 2,
"versionId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"status": "completed",
"batchCode": "LINE-0412",
"startedAt": "2026-09-25T08:02:13.000Z",
"completedAt": "2026-09-25T09:14:02.000Z",
"progress": 0,
"steps": []
}
}| Response header | Meaning |
|---|---|
Idempotent-Replay | true when this response is a replay of an earlier request with the same Idempotency-Key. |
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 |
|---|---|---|
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'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, the run id is not a well-formed id, or the key's owner may not close this run because they neither started it nor manage the lab. All three are answered alike. Nothing changes. |
409 | IDEMPOTENCY_IN_FLIGHT | A request with this Idempotency-Key is still being processed; retry after Retry-After seconds. |
409 | SOP_RUN_CLOSED | The run has already ended: its status is one of completed, failed, cancelled, archived. Nothing changes. Get the run to see how it ended. |
422 | VALIDATION_ERROR | The Idempotency-Key header is malformed. |
422 | VALIDATION_ERROR | A field failed validation: outcome is missing or is not completed, failed or cancelled, or notes is longer than 1,000 characters. error names the field with the first problem, for example outcome: Required. |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | SOP_RUN_UPDATE_FAILED | The run could not be closed. Retry with the same Idempotency-Key. |
Branch on code, never on the error text. See Errors.
Get an SOP run
One run: the SOP and version it follows, where it stands, and the evidence posted against its steps, oldest first — the trail to read forwards when you need to know what was done, in what order.
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.