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

Creates a design — a name, an optional description, and the paper format. Create allocates the design only; it does not author content. 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.
sizestringnoA4 (default), A3, A5, Letter, Legal.
orientationstringnoportrait (default) or landscape.
widthnumbernoCustom page width in points. Mutually exclusive with size.
heightnumbernoCustom page height in points. Must be sent with width.

The design’s first page is born at the requested format, so set it here rather than flipping the page afterwards. width/height are taken literally — orientation is ignored for a custom sheet — and sending both a size and custom dimensions is a 400. To change the paper on an existing design, use the update_page action.

The body is strict: any unknown key, actions included, is a loud 400 rather than a silently-ignored create.

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/api/v1/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/api/v1/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/api/v1/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/api/v1/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/api/v1/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, table, shape, icon, qr, barcode). The id is server-minted; give it a unique name.
update_elementMerge changes into one element (by name or id); newName renames it. isLocked and isHidden also live here — a hidden element is dropped from the rendered PDF, not just the canvas.
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). Pass orientation and the dimensions follow — no separate swap.
update_pageEdit an EXISTING page in place: size, orientation, name, background. This is how you change an existing design’s paper; for a new one, set size/orientation at create time.
remove_pageRemove a page and its elements. The document must keep at least one — removing the last page is rejected.
set_page_backgroundSet or clear (null) one page’s background — backgroundColor (hex or gradient), backgroundImage, backgroundSize. Merged per field.
set_document_backgroundThe same longhands at document level; cascades to pages without their own.
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, icon{ 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.

Data by type

data is the element’s semantic payload; like position, its shape follows the type.

Typedata
text{ content } — a string, or an array of item strings when listFormat is ordered/unordered. Plus listStart, dataType, format.
image{ src } — an uploads:<id> / assets:<id> ref, a data: URI (auto-converted to an upload), or an https:// URL (fetched once and frozen). Optional crop as normalized 0–1 fractions.
table{ rows, columns, columnWidths, width, cells, headerRow, headerColumn }. cells is a 2-D array; each entry is a plain string or a { type, data, styles } cell. Max 500 rows × 50 columns.
shape{ shapeType: "rectangle" | "circle" | "line" | "arrow" }.
icon{ iconId } — an id from the bundled outline-icon catalog, e.g. star, check-circle, mail, map-pin, lock.
qr{ content }.
barcode{ content, format, displayValue }format defaults to CODE128.

The icon catalog is not yet exposed over the API — there is no GET /icons. The ids are the ones the visual editor’s icon picker offers; until the endpoint lands, pick icons there, or use an uploaded SVG, which gives you any artwork you like and is not restricted to a catalog.


Get the action catalog

GET/api/v1/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.


Get the font catalog

GET/api/v1/fonts

Returns every font the product supports. The catalog is deterministic and closed: the editor’s picker offers exactly these, and styles.fontFamily is validated against them — there is no runtime font registration, so a family that is not here cannot be used.

Each entry’s id is the proper family name, and that is the string you put in styles.fontFamily.

curl https://api.imaginepdf.com/api/v1/fonts \ -H "X-API-Key: $IMAGINEPDF_API_KEY"

Response 200 OK

{ "status": "success", "data": { "fonts": [ { "id": "Inter", "class": "sans", "source": "google", "weights": [400, 500, 600, 700], "tags": ["recommended"] }, { "id": "Playfair Display", "class": "serif", "source": "google", "weights": [400, 700] } ] }, "error": null }
FieldNotes
idThe family name. Pass it verbatim as styles.fontFamily.
classsans, serif, display, or handwriting — the picker’s grouping.
sourcegoogle, or bundled for families whose files ship in-repo.
weightsThe weights that family actually has. Asking for one it lacks falls back to the nearest.
tagsrecommended / popular, where present.

Adding a font is a release of the shared pdftreejs catalog, not a runtime registration — so cache this response rather than fetching it per request.

Last updated on