xPlantAPI
API referenceChange history

List change history

What has happened to the workspace's plants or explants, oldest first: transfers, stage changes, contamination records, observations, printed labels.

GET/api/v1/events
Scope read:events

Use it to keep your own system in step with xPlant without downloading everything again:

  1. The first time, call with entity=plant and follow meta.next_cursor until it comes back null. Do the same with entity=explant.
  2. Save the newest created_at you received for each.
  3. Next time, pass that value as since and follow the cursor again from the first page.

Several events can share one created_at — a batch import records many at once. The cursor keeps them in a fixed order, so a walk neither skips nor repeats one; still walk a since window to its last page before you move since forward, and skip any id you already hold.

The history is written by work done in xPlant. Transfers and stage moves recorded through this API's own endpoints do not add events here; their responses already carry what was saved.

See also: Pull change history.

Query parameters

NameTypeRequiredDescription
entity"plant" | "explant"YesWhich history to read. Required: plant and explant histories are separate, so sync each with its own calls.
sincestringNoOnly events recorded after this moment, as an ISO 8601 timestamp such as 2026-09-24T00:00:00Z. Pass the newest created_at you already hold.
limitintegerNoPage size. Values above 200 are capped at 200. Default 50. From 1 to 200.
offsetintegerNoNumber of records to skip. Prefer cursor where a list offers it: an offset shifts when records are added ahead of it. Default 0. At least 0.
cursorstringNoContinue from the previous page: pass its meta.next_cursor unchanged, with the same filters. Treat it as opaque. Not combinable with offset. Up to 2048 characters.

Example

curl "https://app.xplantpro.com/api/v1/events?entity=plant&since=2026-09-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $XPLANT_API_KEY"

Response

200 with { "ok": true, "data": … }. data is an array. To get the next page, pass meta.next_cursor back as cursor; it is null on the last page. See Pagination.

FieldTypeRequiredDescription
idstring (uuid)YesThe event's id. Events are never edited, so an id you already hold is the same event.
entity_type"plant" | "explant"YesWhether the event is about a plant or an explant.
entity_idstring (uuid)YesThe plant's or explant's id.
stage_idstring | null (uuid)YesThe stage the event was recorded against, when there was one.
event_typestringYesWhat happened — for example transfer, stage_change, contamination, observation or label_print. New kinds can appear; treat one you do not recognise as informational.
event_timestringYesWhen it happened in the lab, as an ISO 8601 timestamp.
recorded_bystring | null (uuid)YesThe workspace member who recorded it, when known.
payloadanyYesThe event's details, as a JSON value whose fields depend on event_type — a transfer, for example, carries its cycle, locations and vessel counts. Can be null.
created_atstringYesWhen xPlant recorded the event. Pass the newest one you hold back as since.
Response
{
  "ok": true,
  "data": [
    {
      "id": "4b3a2918-0716-4f5e-8d4c-3b2a19087f6e",
      "entity_type": "explant",
      "entity_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "stage_id": "6e5d4c3b-2a19-4807-96a5-b4c3d2e1f0a9",
      "event_type": "transfer",
      "event_time": "2026-09-24T00:00:00+00:00",
      "recorded_by": "0d7e4b9a-6f21-4c3e-8a5b-2e9f1c7d4a60",
      "payload": {
        "transfer_id": "c7d8e9f0-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
        "transfer_cycle": 5,
        "from_location": "Growth room 1, shelf B2",
        "to_location": "Growth room 1, shelf C1",
        "observed_vessel_count": 12,
        "plantlet_count": 48
      },
      "created_at": "2026-09-24T09:12:45.118Z"
    }
  ]
}

The envelope can also carry meta:

FieldTypeRequiredDescription
next_cursorstring | nullYesPass as cursor to fetch the next page. null means this is the last page.
Response headerMeaning
X-Request-IdIdentifies this request. Include it when you contact support.

Errors

StatusCodeWhen
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.
422VALIDATION_ERRORBoth cursor and offset were sent; use one.
422INVALID_CURSORThe cursor is malformed, or came from a different list or different filters. Start again without it.
422VALIDATION_ERRORentity is missing or is not plant or explant, or since is not a valid timestamp.
429RATE_LIMIT_EXCEEDEDToo many requests for this key, device token or workspace. Wait Retry-After seconds.
500EVENT_QUERY_FAILEDThe history could not be read. Retry later.

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

On this page