Requests and responses
The base URL, headers, and the one response envelope every endpoint uses.
Base URL
https://app.xplantpro.com/api/v1Always call app.xplantpro.com. www.xplantpro.com is the marketing site and answers 401 to every /api path.
Headers
| Header | When | Value |
|---|---|---|
Authorization | Every request | Bearer <workspace key or device token> |
Content-Type | Requests with a body | application/json |
Idempotency-Key | Optional, on writes that support it | A string you choose per logical write |
Every response carries an X-Request-Id header that identifies the request. Log it, and include it when you contact support: it lets us find exactly that call.
Request bodies and responses are JSON. Timestamps are ISO 8601 in UTC, for example 2026-09-25T12:00:00Z. Ids are UUIDs.
The envelope
Every response, from every endpoint, has the same outer shape.
Success
{ "ok": true, "data": { "id": "5b0f6f3e-…", "title": "Transfer the Alocasia line onto fresh medium" } }data holds the result: an object for a single record, an array for a list. Some endpoints add a meta object alongside data to explain something about the result; Update a task is one.
Failure
{ "ok": false, "data": null, "error": "Missing scope: write:tasks", "code": "FORBIDDEN" }codeis stable. Write your error handling against it.erroris written for people. Log it and show it, but don't match on it: the wording can change.- The HTTP status matches the kind of failure (
401,403,404,422,429…).
See Errors for every code.
Reading responses safely
import { XPlantClient, XPlantError } from "@shmaplex/xplant-sdk";
const client = new XPlantClient({ apiKey: process.env.XPLANT_API_KEY });
try {
const tasks = await client.tasks.list({ status: "todo" });
} catch (err) {
if (err instanceof XPlantError) {
// err.status, err.code, err.message, err.retryAfter, err.requestId
if (err.code === "RATE_LIMIT_EXCEEDED") {
// wait err.retryAfter seconds, then try again
}
}
throw err;
}The SDK throws an XPlantError for every API failure, carrying status, code, message and retryAfter. Network failures (no response at all) reject with the underlying fetch error instead.
Workspace scoping
Every request acts inside the workspace the key belongs to. There is no workspace id to pass and no way to reach another workspace. An id from another workspace answers 404, exactly as if it didn't exist.