Skip to Content

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 /designs

Creates 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

FieldTypeRequiredNotes
namestringyesDisplay name.
descriptionstringnoOptional 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 /designs

Returns designs in the workspace, paginated. Folders are excluded.

Query parameters

ParamTypeNotes
parentIdstringFilter to a folder’s children.
cursorstringPagination cursor from a previous nextCursor.
limitnumberPage 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/:id

Returns 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/tree

Returns 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/:id

Rename / 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/tree

Apply 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

ActionDoes
add_elementAdd ONE element (text, image, qr, barcode, shape, table). The id is server-minted; give it a unique name.
update_elementMerge changes into one element (by name or id); newName renames it.
remove_elementRemove one element and any variable bound to it.
reorder_elementChange paint order: to: "front" | "back" | "forward" | "backward" | index.
bind_variableMake one element fillable at generation time (args: just {name} or {id}).
unbind_variableRemove the variable bound to one element.
add_pageAppend a page (width/height/orientation/background).
set_page_backgroundSet or clear (null) a page background.
set_document_backgroundSet or clear the document-level background.
set_metadataSet author / subject / keywords / creator.

Sizing is derived

Clients never set what the system computes. The position shape depends on the element type:

TypepositionSize 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 /actions

Returns 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.

Last updated on