xPlantAPI
API referenceMedia

Attach a media file

Attaches an image to a plant, explant, contamination log or SOP in the workspace. It appears on that record in xPlant exactly like a photo added in the app. Send the image as image_url or as image_base64 — exactly one.

POST/api/v1/assets
Scope write:assetsHonours Idempotency-Key

Limits. JPEG, PNG, WebP or GIF, up to 10 MB. The type is read from the image itself, not from its name. A request body can be at most about 4.5 MB, and base64 makes a file about a third larger, so send image_base64 for images up to about 3 MB and image_url for anything larger. A request body over that size is refused with 413 before it reaches the API.

image_url. xPlant downloads the image once, over https, from the public internet. Addresses on private or local networks are refused, redirects are not followed, and the download must finish within 10 seconds.

The response's view_url works for 15 minutes. A retry with the same Idempotency-Key returns the first response, with a fresh view_url.

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
target"plant" | "explant" | "contamination" | "sop"YesThe kind of record to attach the image to: plant, explant, contamination, sop.
target_idstring (uuid)YesThe id of that record. It must be in the key's workspace.
image_urlstring (uri)NoAn https address xPlant downloads the image from, up to 10 MB. It must be reachable from the public internet: private and local network addresses are refused, and redirects are not followed, so send the image's final address. The download must finish within 10 seconds. Use this for anything larger than about 3 MB. Up to 2048 characters.
image_base64stringNoThe image itself, base64-encoded, without a data: prefix. A request body can be at most about 4.5 MB and base64 makes a file about a third larger, so this suits images up to about 3 MB. Send anything larger, up to the 10 MB limit, as image_url.
filenamestringNoThe name to show for the file, for example leaf-sample-3.jpg. Defaults to the last part of image_url, or photo for an inline image. 1–200 characters.
captionstringNoA short note stored with the image and shown beside it in xPlant. 1–500 characters.

Example

curl -X POST https://app.xplantpro.com/api/v1/assets \
  -H "Authorization: Bearer $XPLANT_API_KEY" \
  -H "Idempotency-Key: camera-2-line-0412-photo-1" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "explant",
    "target_id": "3f9a2c1e-7b4d-4e8f-a6c5-1d2e3f4a5b6c",
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
    "filename": "vessel-12-week-3.png",
    "caption": "Callus forming at the cut edge"
  }'

Response

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

FieldTypeRequiredDescription
idstring (uuid)Yes—
target"plant" | "explant" | "contamination" | "sop"YesThe kind of record the file is attached to.
target_idstring (uuid)YesThe id of that record.
kindstringYesWhat the file is: photo, video, annotation, document, diagram, scan, audio, other. Images attached through the API are photo.
file_namestring | nullYesThe file's name as xPlant shows it.
content_typestring | nullYesThe file's media type, for example image/jpeg, image/png.
captionstring | nullYesThe note stored with the file.
captured_atstring | nullYesWhen the photo was taken, where that was recorded. ISO 8601.
uploaded_bystring | nullYesUser id of the workspace member who added the file.
created_atstring | nullYesWhen the file was added. ISO 8601.
view_urlstring | nullYesA link to the file itself, valid for 15 minutes from this response. Fetch the asset again for a fresh link, and never store one. null when the file cannot be linked.
view_url_expires_atstring | nullYesWhen view_url stops working. ISO 8601.
Response
{
  "ok": true,
  "data": {
    "id": "8c2f1a6e-4b3d-4f7a-9e21-5d6c7b8a9f10",
    "target": "explant",
    "target_id": "3f9a2c1e-7b4d-4e8f-a6c5-1d2e3f4a5b6c",
    "kind": "photo",
    "file_name": "vessel-12-week-3.png",
    "content_type": "image/png",
    "caption": "Callus forming at the cut edge",
    "captured_at": null,
    "uploaded_by": "0d7e4b9a-6f21-4c3e-8a5b-2e9f1c7d4a60",
    "created_at": "2026-09-25T14:20:11.000Z",
    "view_url": "https://files.example.com/vessel-12-week-3.png?signature=4f9c2e",
    "view_url_expires_at": "2026-09-25T14:35:11.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.
409IDEMPOTENCY_IN_FLIGHTA request with this Idempotency-Key is still being processed; retry after Retry-After seconds.
413PAYLOAD_TOO_LARGEThe image is larger than 10 MB.
415UNSUPPORTED_MEDIA_TYPEThe file is not a JPEG, PNG, WebP or GIF image. The type is read from the file itself.
422VALIDATION_ERRORThe Idempotency-Key header is malformed.
422VALIDATION_ERRORA field failed validation. error names the first one, for example title: title is required.
422VALIDATION_ERRORtarget_id does not name a record of that kind in the key's workspace.
422VALIDATION_ERRORimage_url is not an https address on the public internet.
422IMAGE_URL_FETCH_FAILEDimage_url could not be downloaded: it redirected, answered with an error status, took longer than 10 seconds, or its host could not be found. error says which.
429RATE_LIMIT_EXCEEDEDToo many requests for this key, device token or workspace. Wait Retry-After seconds.
500ASSET_UPLOAD_FAILEDThe image could not be stored. Retry with the same Idempotency-Key.
500ASSET_INSERT_FAILEDThe image could not be saved to the record. Retry with the same Idempotency-Key.

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

On this page