Add a comment
Adds a comment to a plant, explant, contamination log, task, media recipe or SOP, as the key's owner. It is the same comment the app makes: members it names are notified, the author of the comment it replies to is told, the people following a task hear about it, and it appears in the record's activity.
https://app.xplantpro.com/ api/ v1/ commentswrite:commentsHonours Idempotency-KeyThe key's owner needs the member role or above in the workspace, as in the app.
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 |
|---|---|---|---|
entity_type | "plant" | "explant" | "contamination" | "task" | "media_recipe" | "sop" | Yes | The kind of record to comment on. |
entity_id | string (uuid) | Yes | The id of the record to comment on. |
body | string | Yes | The comment, in Markdown. Stored exactly as sent. To mention someone, write @ and their username in the text and add their user id to mentioned_user_ids. To link a record, write #plant: (or #explant:, #contamination:, #task:, #media:, #sop:) followed by its name, and list it in references. 1–5000 characters. |
parent_id | string (uuid) | No | Reply to this comment. It must be on the same record. |
mentioned_user_ids | string (uuid)[] | No | Workspace members this comment names. Each is notified, exactly as when someone is mentioned in the app. Every id must be an active member of the workspace. Up to 25 items. |
references | object[] | No | Records in this workspace that the body links to. Up to 25 items. |
references[].entity_type | "plant" | "explant" | "contamination" | "task" | "media_recipe" | "sop" | Yes | The kind of record the comment links to. |
references[].entity_id | string (uuid) | Yes | The linked record's id. |
references[].label | string | Yes | The record's name as it follows #type: in the body, where spaces are written as hyphens; either spelling is accepted here. The link is drawn over the matching text. 1–200 characters. |
Example
curl -X POST https://app.xplantpro.com/api/v1/comments \
-H "Authorization: Bearer $XPLANT_API_KEY" \
-H "Idempotency-Key: bench-3-line-0412-note-1" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "explant",
"entity_id": "6d2f9a14-8b3e-4c71-a5d0-1f7e3b8c2a96",
"body": "@sam the Alocasia line on shelf 2 is ready to go onto fresh medium. See #task:Subculture-Alocasia-line.",
"mentioned_user_ids": [
"4a8f1c63-2e7d-4b95-8c10-5d3e9f7a2b68"
],
"references": [
{
"entity_type": "task",
"entity_id": "5b0f6f3e-2c1a-4d8e-9f47-0a6c3e1b7d22",
"label": "Subculture Alocasia line"
}
]
}'Response
201 with { "ok": true, "data": … }. data holds the result.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | — |
entity_type | "plant" | "explant" | "contamination" | "task" | "media_recipe" | "sop" | Yes | The kind of record the comment is on. |
entity_id | string (uuid) | Yes | The record the comment is on. |
parent_id | string | null (uuid) | Yes | The comment this one replies to. |
body | string | Yes | The comment in Markdown, exactly as written — mentions and record links included as the @name and #type:Label text the author typed. |
status | string | Yes | active; edited once changed after posting; or deleted, which keeps the comment's place in the thread but not its text. |
is_pinned | boolean | Yes | Pinned to the top of the record's discussion. |
author | object | Yes | — |
author.id | string (uuid) | Yes | User id of the author. |
author.name | string | null | Yes | The name shown beside the comment in the app, or null when there is none to show. |
mentioned_user_ids | string (uuid)[] | Yes | Workspace members the comment named. Each was notified. |
references | object[] | Yes | Records in the workspace the body links to. A record removed since is left out. |
references[].entity_type | "plant" | "explant" | "contamination" | "task" | "media_recipe" | "sop" | Yes | — |
references[].entity_id | string (uuid) | Yes | — |
references[].label | string | Yes | The name the body links from. |
created_at | string | null | Yes | ISO 8601. |
updated_at | string | null | Yes | ISO 8601. |
edited_at | string | null | Yes | When the text was last changed. ISO 8601. |
{
"ok": true,
"data": {
"id": "9c4e2a71-3d5b-4f86-b1e0-7a2d6c8f3e45",
"entity_type": "explant",
"entity_id": "6d2f9a14-8b3e-4c71-a5d0-1f7e3b8c2a96",
"parent_id": null,
"body": "@sam the Alocasia line on shelf 2 is ready to go onto fresh medium. See #task:Subculture-Alocasia-line.",
"status": "active",
"is_pinned": false,
"author": {
"id": "0d7e4b9a-6f21-4c3e-8a5b-2e9f1c7d4a60",
"name": "Sam Okafor"
},
"mentioned_user_ids": [
"4a8f1c63-2e7d-4b95-8c10-5d3e9f7a2b68"
],
"references": [
{
"entity_type": "task",
"entity_id": "5b0f6f3e-2c1a-4d8e-9f47-0a6c3e1b7d22",
"label": "Subculture Alocasia line"
}
],
"created_at": "2026-09-25T09:15:40.000Z",
"updated_at": "2026-09-25T09:15:40.000Z",
"edited_at": null
}
}| 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 | entity_id names no record of that entity_type in the workspace, or parent_id names no comment on it; error says which. |
409 | IDEMPOTENCY_IN_FLIGHT | A request with this Idempotency-Key is still being processed; retry after Retry-After seconds. |
409 | CONFLICT | The task's discussion is locked. Only the task's creator or a lab manager can comment. |
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 | A mentioned user is not an active member of the workspace, or a linked record is not in it. |
429 | RATE_LIMIT_EXCEEDED | Too many requests for this key, device token or workspace. Wait Retry-After seconds. |
500 | COMMENT_CREATE_FAILED | The comment could not be saved. Retry with the same Idempotency-Key. |
Branch on code, never on the error text. See Errors.
List comments
The discussion on one plant, explant, contamination log, task, media recipe or SOP, oldest first. Replies carry parent_id. A deleted comment keeps its place, with its text removed, so the replies to it still read in order.
List media files
The photos and files attached to one plant, explant, contamination log or SOP, newest first. Each carries a view_url: a link to the file itself that works for 15 minutes. List again for fresh links, and never store one.