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
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
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. |
description | string | no | Optional description. |
size | string | no | A4 (default), A3, A5, Letter, Legal. |
orientation | string | no | portrait (default) or landscape. |
width | number | no | Custom page width in points. Mutually exclusive with size. |
height | number | no | Custom 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
Returns 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)
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
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)
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
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
| Action | Does |
|---|---|
add_element | Add ONE element (text, image, table, shape, icon, qr, barcode). The id is server-minted; give it a unique name. |
update_element | Merge 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_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). Pass orientation and the dimensions follow — no separate swap. |
update_page | Edit 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_page | Remove a page and its elements. The document must keep at least one — removing the last page is rejected. |
set_page_background | Set or clear (null) one page’s background — backgroundColor (hex or gradient), backgroundImage, backgroundSize. Merged per field. |
set_document_background | The same longhands at document level; cascades to pages without their own. |
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, 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.
| Type | data |
|---|---|
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
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
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
}| Field | Notes |
|---|---|
id | The family name. Pass it verbatim as styles.fontFamily. |
class | sans, serif, display, or handwriting — the picker’s grouping. |
source | google, or bundled for families whose files ship in-repo. |
weights | The weights that family actually has. Asking for one it lacks falls back to the nearest. |
tags | recommended / 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.