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.
https://app.xplantpro.com/ api/ v1/ sop-runs/ {id}/ steps/ {stepId}/ eventswrite:sop_stepsHonours Idempotency-KeyEvidence is append-only. There is no edit or delete — to correct something, post another event that says so. Send an Idempotency-Key so a scanner retrying on a patchy connection records one event rather than two.
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. |
stepId | string | Yes | The step's id in the version the run follows, as Get an SOP lists it in version.steps. It is recorded as sent and not checked against the procedure. |
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 |
|---|---|---|---|
event_type | "confirmed" | "scanned" | "skipped" | "note" | "device_state" | Yes | What happened at the step: confirmed (it was done), scanned (a label or vessel was scanned), skipped, note (a remark for whoever reads the run later) or device_state (what an instrument reported, such as a hood or autoclave cycle). |
payload | object | No | Any detail worth keeping with the event, as a JSON object — the code that was scanned, the note's text, the device's reading. Stored as sent. Default {}. |
recorded_at | string (date-time) | No | When it happened, in UTC with a trailing Z, such as 2026-09-25T08:30:00Z. A timestamp with an offset is refused. Defaults to when xPlant receives it. |
Example
curl -X POST https://app.xplantpro.com/api/v1/sop-runs/2a4c6e8b-1d3f-4b5a-a7c9-e1f3a5b7c9d1/steps/step-3/events \
-H "Authorization: Bearer $XPLANT_API_KEY" \
-H "Idempotency-Key: station-3-wk38-step3-scan" \
-H "Content-Type: application/json" \
-d '{
"event_type": "scanned",
"payload": {
"code": "LINE-0412-J07"
},
"recorded_at": "2026-09-25T08:30:00Z"
}'Response
201 with { "ok": true, "data": … }. data holds the result.
| 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": "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"
}
}
}| 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'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. |
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 is complete. A completed run's record is final and takes no more evidence. |
422 | VALIDATION_ERROR | The Idempotency-Key header is malformed. |
422 | VALIDATION_ERROR | A field failed validation — an event_type outside the list, or a recorded_at that is not a UTC timestamp. error gives the first problem but does not name the field. |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | SOP_STEP_EVENT_CREATE_FAILED | The evidence could not be saved. 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 every piece of evidence posted against its steps, oldest first — the trail to read forwards when you need to know what was done, in what order.
Record a step measurement
Records a numeric reading against one step of a run — the pH of a medium, the mass of an ingredient, a temperature — with its unit, as the bench station running the SOP reads it from a balance, pH meter or probe.