Record a contamination
Records a contamination seen on one plant or explant, as the key's owner. It is the same record the app makes: it appears in the plant's or explant's history, and for an explant the stage and room the culture was in when it was seen are recorded with it, which is what lets the app show that room's conditions over the week before.
https://app.xplantpro.com/ api/ v1/ contaminationswrite:contaminationsHonours Idempotency-KeySend observed_at when logging something seen earlier; it defaults to now. Fill in the fields your lab has added to contaminations with custom_fields; they are checked against your lab's field list exactly as the app's form checks them.
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 |
|---|---|---|---|
plant_id | string (uuid) | No | The plant the contamination was seen on. Send exactly one of plant_id or explant_id. |
explant_id | string (uuid) | No | The explant the contamination was seen on. Send exactly one of plant_id or explant_id. |
type | "mold" | "bacteria" | "hyperhydricity" | "phenolic" | "algae" | "yeast" | "endophytic" | "viral" | "fungal" | "physiological" | "contaminated_media" | "damage" | "insect" | "other" | Yes | What was seen. Use other and describe it in type_other when nothing in the list fits. |
type_other | string | No | What the contamination is, in your own words. Required when type is other, ignored otherwise. 1–200 characters. |
issue | string | Yes | A short summary of what was seen, as it should read in a list. 1–300 characters. |
description | string | No | Up to 5000 characters. |
notes | string | No | Up to 5000 characters. |
severity | "very low" | "low" | "medium" | "high" | "critical" | No | Default "low". |
status | "active" | "resolved" | "quarantined" | "archived" | "under investigation" | No | Where the contamination stands. A new observation is active; send another state only when recording one that has already been dealt with. Default "active". |
suspected_source | "airborne" | "cross" | "media" | "observed" | "tool" | "transferred" | "unknown" | No | Where you think it came from, if you have a view. Leave it out rather than guess. |
observed_at | string (date-time) | No | When it was seen, as an ISO 8601 timestamp. Defaults to now. For an explant, the stage and room it was in at that moment are recorded with the observation. |
vessels_affected | integer | No | How many vessels it reached. Leave it out if nobody counted: 0 means counted and none were affected. From 0 to 1000000. |
plants_affected | integer | No | How many plants it reached. Leave it out if nobody counted: 0 means counted and none were affected. From 0 to 1000000. |
affected_vessel_markings | string | No | Which vessels, as written on them — for example B-12 / B-14. Up to 500 characters. |
custom_fields | object | null | No | Values for the fields your lab has added to contaminations, keyed by field key — for example { "hood": "Hood 2" }. Each value must suit its field: text, a number, true or false, a date (stored as YYYY-MM-DD), or one of a list field's options. null leaves a field empty. A key your lab has not defined is refused. |
Example
curl -X POST https://app.xplantpro.com/api/v1/contaminations \
-H "Authorization: Bearer $XPLANT_API_KEY" \
-H "Idempotency-Key: scanner-3-line-0412-contamination" \
-H "Content-Type: application/json" \
-d '{
"explant_id": "6d2f9a14-8b3e-4c71-a5d0-1f7e3b8c2a96",
"type": "bacteria",
"issue": "Cloudy halo around the base of the Phalaenopsis explant",
"notes": "Noticed at the weekly check on shelf 3.",
"severity": "medium",
"observed_at": "2026-09-24T08:30:00Z",
"vessels_affected": 2,
"affected_vessel_markings": "P-07 / P-09",
"custom_fields": {
"hood": "Hood 2"
}
}'Response
201 with { "ok": true, "data": … }. data holds the result.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | — |
workspace_id | string | null | Yes | The workspace the log belongs to. |
type | string | null | Yes | What was seen: mold, bacteria, hyperhydricity, phenolic, algae, yeast, endophytic, viral, fungal, physiological, contaminated_media, damage, insect, other. |
type_other | string | null | Yes | The contamination in the logger's own words, when type is other. |
issue | string | Yes | A short summary of what was seen. |
description | string | null | Yes | — |
notes | string | null | Yes | — |
severity | string | Yes | How serious it is: very low, low, medium, high, critical. |
status | string | Yes | Where it stands: active, resolved, quarantined, archived, under investigation. |
observed_at | string | null | Yes | When it was seen. ISO 8601. |
resolved_at | string | null | Yes | When it was marked resolved. ISO 8601. |
vessels_affected | integer | null | Yes | How many vessels it reached. Null when nobody counted, which is not the same as none. |
plants_affected | integer | null | Yes | How many plants it reached. Null when nobody counted, which is not the same as none. |
affected_vessel_markings | string | null | Yes | Which vessels, as written on them. |
custom_fields | object | Yes | Your lab's own fields on this contamination, keyed by field key. Always an object: {} when none were filled in. |
plant_ids | string (uuid)[] | Yes | The plants the log is linked to. |
explant_ids | string (uuid)[] | Yes | The explants the log is linked to. |
logged_by | string (uuid) | Yes | User id of the workspace member who logged it. |
created_at | string | null | Yes | When it was logged. ISO 8601. |
updated_at | string | null | Yes | When it last changed. ISO 8601. |
{
"ok": true,
"data": {
"id": "8a3c1e57-4b2d-4f60-9c81-2d7e5b9a0f14",
"workspace_id": "3f9d2b61-7c4e-4a85-b0d3-6e1f8a2c5b97",
"type": "bacteria",
"type_other": null,
"issue": "Cloudy halo around the base of the Phalaenopsis explant",
"description": null,
"notes": "Noticed at the weekly check on shelf 3.",
"severity": "medium",
"status": "active",
"observed_at": "2026-09-24T08:30:00.000Z",
"resolved_at": null,
"vessels_affected": 2,
"plants_affected": null,
"affected_vessel_markings": "P-07 / P-09",
"custom_fields": {
"hood": "Hood 2"
},
"plant_ids": [],
"explant_ids": [
"6d2f9a14-8b3e-4c71-a5d0-1f7e3b8c2a96"
],
"logged_by": "0d7e4b9a-6f21-4c3e-8a5b-2e9f1c7d4a60",
"created_at": "2026-09-24T08:41:12.000Z",
"updated_at": "2026-09-24T08:41:12.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'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. |
403 | FORBIDDEN | The key's owner is below the member role in the workspace. A key never does more than its owner can in xPlant; error names the role needed. |
404 | NOT_FOUND | plant_id or explant_id names no plant or explant in the key's workspace; error says which. |
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 title: title is required. |
422 | VALIDATION_ERROR | custom_fields names a field your lab has not defined, or gives a field a value of the wrong kind. error names the field. |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | CONTAMINATION_CREATE_FAILED | The log could not be saved, and nothing was kept. Retry with the same Idempotency-Key. |
Branch on code, never on the error text. See Errors.