Designs API
Designs are document layouts. You build and edit them by sending actions —
each action is { type, args } and does one thing (add one element, bind one
variable). Actions apply sequentially and atomically; see
Designs for the concept. All paths below are relative to
https://api.imaginepdf.com/api/v1.
Create a design
POST /designsCreates a design — a name and an optional description. Create allocates
the design only; it does not author content. A new design starts with one
blank A4 page; you build it by sending actions to
PATCH /designs/:id/tree. Posting actions to this endpoint
is rejected with a 400 — creation and authoring are separate steps.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. |
description | string | no | Optional description. |
curl -X POST https://api.imaginepdf.com/api/v1/designs \
-H "X-API-Key: $IMAGINEPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice",
"description": "Standard invoice template"
}'Response 201 Created
{
"status": "success",
"data": {
"designId": "design_abc123",
"name": "Invoice",
"description": "Standard invoice template",
"createdAt": "2026-06-08T12:00:00Z"
},
"error": null
}Take the designId and author the design with
PATCH /designs/:id/tree: it applies your actions batch and
returns one result per action — each echoing the minted id and the
derived box (no element id or w/h is ever sent; both come back).
List designs
GET /designsReturns designs in the workspace, paginated. Folders are excluded.
Query parameters
| Param | Type | Notes |
|---|---|---|
parentId | string | Filter to a folder’s children. |
cursor | string | Pagination cursor from a previous nextCursor. |
limit | number | Page size, 1–200. |
curl "https://api.imaginepdf.com/api/v1/designs?limit=50" \
-H "X-API-Key: $IMAGINEPDF_API_KEY"Response 200 OK
{
"status": "success",
"data": {
"items": [
{
"designId": "design_abc123",
"name": "Invoice",
"description": "Standard invoice template",
"fileSize": 12345,
"createdAt": "2026-06-08T12:00:00Z",
"modifiedAt": "2026-06-08T12:00:00Z"
}
],
"nextCursor": null
},
"error": null
}When nextCursor is non-null, pass it back as cursor to fetch the next page.
Get a design (metadata)
GET /designs/:idReturns the design’s metadata only — not the tree. Cheap: read names and
timestamps without pulling the whole layout. Fetch the layout separately with
GET /designs/:id/tree.
curl https://api.imaginepdf.com/api/v1/designs/design_abc123 \
-H "X-API-Key: $IMAGINEPDF_API_KEY"Response 200 OK
{
"status": "success",
"data": {
"designId": "design_abc123",
"name": "Invoice",
"description": "Standard invoice template",
"fileSize": 12345,
"createdAt": "2026-06-08T12:00:00Z",
"modifiedAt": "2026-06-08T12:00:00Z"
},
"error": null
}Get the design tree
GET /designs/:id/treeReturns the full design tree (the pdftreejs PDFTree) as raw JSON. This is
the one endpoint that does not use the {status, data, error} envelope — the
response body IS the tree (errors still come back enveloped). Fetch it to inspect
the layout before sending a tree patch.
curl https://api.imaginepdf.com/api/v1/designs/design_abc123/tree \
-H "X-API-Key: $IMAGINEPDF_API_KEY"Response 200 OK (raw — no envelope)
{
"metadata": { "schemaVersion": "v1" },
"pages": [ /* ... */ ],
"nodes": { /* ... */ }
}Update a design (metadata)
PATCH /designs/:idRename / re-describe. Metadata only — it never touches the tree. Provide
at least one of name, description.
curl -X PATCH https://api.imaginepdf.com/api/v1/designs/design_abc123 \
-H "X-API-Key: $IMAGINEPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Invoice (2026)", "description": "Updated template" }'Response 200 OK — { designId, name, description, fileSize, modifiedAt }.
Update the tree
PATCH /designs/:id/treeApply an ordered batch of authoring actions — including the first batch on a
freshly-created design. Body: { "actions": [ … ] } (1–500 actions).
curl -X PATCH https://api.imaginepdf.com/api/v1/designs/design_abc123/tree \
-H "X-API-Key: $IMAGINEPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{
"type": "update_element",
"args": {
"name": "title",
"data": { "content": "INVOICE #2026-001" }
}
},
{ "type": "bind_variable", "args": { "name": "title" } }
]
}'Existing elements are addressed by their unique name (or by the minted id —
exactly one of the two). The batch is atomic: if any action fails, nothing
is saved and the error names the failing index, e.g.
actions[1] (bind_variable) failed: ….
Response 200 OK
{
"status": "success",
"data": {
"designId": "design_abc123",
"fileSize": 13002,
"modifiedAt": "2026-06-08T12:05:00Z",
"results": [ /* one result per action, in order */ ]
},
"error": null
}The actions
| Action | Does |
|---|---|
add_element | Add ONE element (text, image, qr, barcode, shape, table). The id is server-minted; give it a unique name. |
update_element | Merge changes into one element (by name or id); newName renames it. |
remove_element | Remove one element and any variable bound to it. |
reorder_element | Change paint order: to: "front" | "back" | "forward" | "backward" | index. |
bind_variable | Make one element fillable at generation time (args: just {name} or {id}). |
unbind_variable | Remove the variable bound to one element. |
add_page | Append a page (width/height/orientation/background). |
set_page_background | Set or clear (null) a page background. |
set_document_background | Set or clear the document-level background. |
set_metadata | Set author / subject / keywords / creator. |
Sizing is derived
Clients never set what the system computes. The position shape depends on the
element type:
| Type | position | Size comes from |
|---|---|---|
text | { x, y, maxWidth? } | Content + fontSize + lineHeight. maxWidth (points) pins the box width exactly — content wraps inside it; set it for paragraphs and for right/center-aligned text so the aligned edge stays put. Omit it and the box hugs the content. |
table | { x, y } | The grid: data.columnWidths (points, one per column) or data.width (total, split equally); row heights follow cell content. |
qr | { x, y, size } | Always square. |
image, barcode, shape | { x, y, w, h } | Explicit box. |
Sending w/h for a text or table is rejected with an error that names the
fix (e.g. position.w is not allowed for text — width is derived; use position.maxWidth). Every add_element / update_element result echoes the
final derived box.
Get the action catalog
GET /actionsReturns every authoring action and its args schema. Use this to discover what
actions[].args should contain.
curl https://api.imaginepdf.com/api/v1/actions \
-H "X-API-Key: $IMAGINEPDF_API_KEY"Response 200 OK
{
"status": "success",
"data": {
"actions": [
{ "name": "add_element", "description": "...", "args": { /* schema */ } },
{ "name": "bind_variable", "description": "...", "args": { /* schema */ } }
]
},
"error": null
}Action args are validated server-side by pdftreejs, strictly — unknown keys
are rejected with a message that says the fix. The catalog at GET /actions
is the machine-readable source of truth; treat it as authoritative rather
than hard-coding shapes from these examples.