xPlantAPI
API referenceContaminations

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.

POST/api/v1/contaminations
Scope write:contaminationsHonours Idempotency-Key

Send 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

NameTypeRequiredDescription
Idempotency-KeystringNoAny 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

FieldTypeRequiredDescription
plant_idstring (uuid)NoThe plant the contamination was seen on. Send exactly one of plant_id or explant_id.
explant_idstring (uuid)NoThe 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"YesWhat was seen. Use other and describe it in type_other when nothing in the list fits.
type_otherstringNoWhat the contamination is, in your own words. Required when type is other, ignored otherwise. 1–200 characters.
issuestringYesA short summary of what was seen, as it should read in a list. 1–300 characters.
descriptionstringNoUp to 5000 characters.
notesstringNoUp to 5000 characters.
severity"very low" | "low" | "medium" | "high" | "critical"NoDefault "low".
status"active" | "resolved" | "quarantined" | "archived" | "under investigation"NoWhere 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"NoWhere you think it came from, if you have a view. Leave it out rather than guess.
observed_atstring (date-time)NoWhen 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_affectedintegerNoHow many vessels it reached. Leave it out if nobody counted: 0 means counted and none were affected. From 0 to 1000000.
plants_affectedintegerNoHow many plants it reached. Leave it out if nobody counted: 0 means counted and none were affected. From 0 to 1000000.
affected_vessel_markingsstringNoWhich vessels, as written on them — for example B-12 / B-14. Up to 500 characters.
custom_fieldsobject | nullNoValues 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.

FieldTypeRequiredDescription
idstring (uuid)Yes—
workspace_idstring | nullYesThe workspace the log belongs to.
typestring | nullYesWhat was seen: mold, bacteria, hyperhydricity, phenolic, algae, yeast, endophytic, viral, fungal, physiological, contaminated_media, damage, insect, other.
type_otherstring | nullYesThe contamination in the logger's own words, when type is other.
issuestringYesA short summary of what was seen.
descriptionstring | nullYes—
notesstring | nullYes—
severitystringYesHow serious it is: very low, low, medium, high, critical.
statusstringYesWhere it stands: active, resolved, quarantined, archived, under investigation.
observed_atstring | nullYesWhen it was seen. ISO 8601.
resolved_atstring | nullYesWhen it was marked resolved. ISO 8601.
vessels_affectedinteger | nullYesHow many vessels it reached. Null when nobody counted, which is not the same as none.
plants_affectedinteger | nullYesHow many plants it reached. Null when nobody counted, which is not the same as none.
affected_vessel_markingsstring | nullYesWhich vessels, as written on them.
custom_fieldsobjectYesYour lab's own fields on this contamination, keyed by field key. Always an object: {} when none were filled in.
plant_idsstring (uuid)[]YesThe plants the log is linked to.
explant_idsstring (uuid)[]YesThe explants the log is linked to.
logged_bystring (uuid)YesUser id of the workspace member who logged it.
created_atstring | nullYesWhen it was logged. ISO 8601.
updated_atstring | nullYesWhen it last changed. ISO 8601.
Response
{
  "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 headerMeaning
Idempotent-Replaytrue when this response is a replay of an earlier request with the same Idempotency-Key.
X-Request-IdIdentifies this request. Include it when you contact support.

Errors

StatusCodeWhen
400VALIDATION_ERRORThe request body is not valid JSON.
401UNAUTHORIZEDThe key is missing, malformed or revoked, or its owner is no longer a member of the workspace.
402PAID_PLAN_REQUIREDThe 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.
403FORBIDDENThe 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.
403DEVICE_TOKEN_NOT_ACCEPTEDA device token was sent; this operation needs a workspace API key.
403FORBIDDENThe 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.
404NOT_FOUNDplant_id or explant_id names no plant or explant in the key's workspace; error says which.
409IDEMPOTENCY_IN_FLIGHTA request with this Idempotency-Key is still being processed; retry after Retry-After seconds.
422VALIDATION_ERRORThe Idempotency-Key header is malformed.
422VALIDATION_ERRORA field failed validation. error names the first one, for example title: title is required.
422VALIDATION_ERRORcustom_fields names a field your lab has not defined, or gives a field a value of the wrong kind. error names the field.
429RATE_LIMIT_EXCEEDEDToo many requests for this key, device token or workspace. Wait Retry-After seconds.
500CONTAMINATION_CREATE_FAILEDThe 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.

On this page