xPlantAPI
API referenceTransfers and stages

Record a transfer

Records a transfer — a subculture onto fresh media — for one plant or explant. Send exactly one of plant_id or explant_id. Everything else is optional: the date defaults to today and the cycle to the next one in the record's history.

POST/api/v1/transfers
Scope write:transfersHonours Idempotency-Key

Fill in the fields your lab has added to transfers with custom_fields. They are checked against your lab's field list exactly as the transfer form in xPlant checks them, so a key the lab has not defined, or a value of the wrong kind, is refused before anything is saved.

Send an Idempotency-Key when you might retry: a repeated transfer would also advance the cycle twice.

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 this is for. Send exactly one of plant_id or explant_id.
explant_idstring (uuid)NoThe explant this is for. Send exactly one of plant_id or explant_id.
transfer_datestringNoWhen the transfer was done: a date such as 2026-09-24, or a full ISO 8601 timestamp. Defaults to today (UTC).
from_locationstringNoWhere the culture was before the transfer, in your lab's own words — a room, shelf or hood. Up to 200 characters.
to_locationstringNoWhere the culture went. Up to 200 characters.
transfer_cycleintegerNoWhich subculture this is for the plant or explant — 1 for the first. Defaults to one more than the highest cycle already recorded for it. At least 1.
notesstringNoUp to 5000 characters.
custom_fieldsobject | nullNoValues for the fields your lab has added to transfers, keyed by field key — for example { "vessel_lot": "LOT-2291" }. 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, and the values may total at most 10,000 bytes. Explant transfers only: a plant transfer takes no custom fields.

Example

curl -X POST https://app.xplantpro.com/api/v1/transfers \
  -H "Authorization: Bearer $XPLANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "explant_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "transfer_date": "2026-09-24",
    "from_location": "Growth room 1, shelf B2",
    "to_location": "Growth room 1, shelf C1",
    "notes": "LINE-0412 onto fresh multiplication medium. Two jars browning at the base, set aside.",
    "custom_fields": {
      "vessel_lot": "LOT-2291",
      "hood": "Hood 2"
    }
  }'

Response

201 with { "ok": true, "data": … }. data holds the result.

FieldTypeRequiredDescription
idstring (uuid)Yes—
entity_type"plant" | "explant"YesWhether the transfer was of a plant or an explant.
entity_idstring (uuid)YesThe plant's or explant's id.
transfer_datestring | nullYesWhen the transfer was done, as an ISO 8601 timestamp.
transfer_cycleinteger | nullYesWhich subculture this was for the plant or explant — 1 for the first.
from_locationstring | nullYesWhere the culture was before the transfer.
to_locationstring | nullYesWhere the culture went.
statusstringYesactive for a transfer recorded through the API. Transfers scheduled in xPlant read pending until someone marks them completed or cancelled.
notesstring | nullYes—
created_atstring | nullYes—
custom_fieldsobjectYesYour lab's own fields on this transfer, keyed by field key. Always an object: {} when none were filled in, and always {} on a plant transfer.
Response
{
  "ok": true,
  "data": {
    "id": "c7d8e9f0-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
    "entity_type": "explant",
    "entity_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "transfer_date": "2026-09-24T00:00:00+00:00",
    "transfer_cycle": 5,
    "from_location": "Growth room 1, shelf B2",
    "to_location": "Growth room 1, shelf C1",
    "status": "active",
    "notes": "LINE-0412 onto fresh multiplication medium. Two jars browning at the base, set aside.",
    "created_at": "2026-09-24T09:12:44.000Z",
    "custom_fields": {
      "vessel_lot": "LOT-2291",
      "hood": "Hood 2"
    }
  }
}
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.
404NOT_FOUNDThe plant or explant is not in this workspace.
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, or neither or both of plant_id and explant_id were sent. error names the first problem, for example transfer_cycle: Number must be greater than or equal to 1.
422VALIDATION_ERRORcustom_fields names a field your lab has not defined, gives a field a value of the wrong kind, or was sent for a plant transfer. error names the field.
429RATE_LIMIT_EXCEEDEDToo many requests for this key, device token or workspace. Wait Retry-After seconds.
500TRANSFER_CREATE_FAILEDThe transfer could not be saved. Retry with the same Idempotency-Key.

Branch on code, never on the error text. See Errors.

On this page