Skip to content

Design templates — document templates and PDF rendering

DD7 lets an integration discover the same published template that the Editor picker uses. The REST v1 routes and MCP tools return a portable field contract and expose no database IDs or mutable full-document body.

Use a token with catalog:read scope and workspace WsAssetsRead permission:

GET /api/v1/design-templates
GET /api/v1/design-templates/{tpl_id}
GET /api/v1/design-templates/{tpl_id}/versions
GET /api/v1/design-templates/{tpl_id}/versions/{revision}/contract

tpl_id is an opaque tpl_ prefixed GUID. The list contains published templates in the caller’s workspace, with name, status, page_count and current_revision. The contract endpoint returns the exact immutable revision, page count, hashes and fields with each placeholder’s code, type, required and optional default. Use the field codes as keys of the JSON object connected to the design/template_render data port. For the two-page proposal example, the field keys are customer and headline; a named headline port overrides the value in data.

data accepts several objects: connect them to data_0, data_1, … and they are merged in index order, a later object overriding an earlier one’s keys (a connection to the unindexed data is read first). Use it to keep AI-written copy and verified facts apart — for example the copy produced by an LLM on data_0 and the offer maintained by the business on data_1 — instead of splitting the facts into one field input each. Precedence overall: named field inputs, then assets, then the merged data.

{ "customer": "Acme Studio", "headline": "Proposal for Acme Studio" }

A json field read by a Repeat region (a list printed one row per item) also lists item_fields: the keys each item provides, with code, name, type and required. The data shape does not change; for a price list whose row reads name and price:

{ "items": [ { "name": "Espresso", "price": "1.20" }, { "name": "Tea", "price": "1.80" } ] }

With missing_policy fail_required (the default), an item without a required key stops the render with DESIGN_PLACEHOLDER_REQUIRED and the item path, e.g. items[1].name.

The /versions route lists published revisions newest first with a current marker and portable tplv_ IDs. Choose a revision from this list before requesting its field contract. To author a workflow via REST v1, MCP, or an agent, set design/template_render.parameters.template_id to the discovered tpl_ ID and optionally set template_revision to a published revision number. If you omit template_revision, Madoo fixes the currently published revision during workflow validation and creation. The definition readback displays the revision it fixed. The server resolves the internal document and version IDs and derives the typed field input ports; the caller never needs to read the Editor’s numeric IDs or supply templateContract.

{"id":"render_proposal","type":"design/template_render","parameters":{
"template_id":"tpl_205ed88f82fd2ce57f3587b579705094",
"output":"both"
}}

Two other nodes fill a template, selected the same way (parameters.template_id, optional template_revision, never documentId). Their input ports are the template’s field codes, typed like the fields; connect only the fields to fill, the others keep the value authored in the template.

  • document/pdf renders one PDF. For one document design/template_render is generally preferable (structured data, pinned revision, page images, layout report, PDF/X-4).
  • aggregate/pdf turns an iteration into one PDF: placed after the per-item branch of an enumerator, it fills the template once per item and appends each item’s pages, with optional introPdf/outroPdf pages (a cover rendered by document/pdf or design/template_render). Use it for a catalog, a price list or one sheet per product; utility/merge_pdf only joins a fixed list of existing PDFs.

Unlike the renderer these two nodes are not pinned: without template_revision they render the template’s current draft when the workflow runs, so later draft edits change the output. Set template_revision for a reproducible workflow.

{"id":"catalog","type":"aggregate/pdf","parameters":{
"template_id":"tpl_205ed88f82fd2ce57f3587b579705094","template_revision":3,"outputName":"catalog"
}}

A different workspace cannot resolve the ID; the lookup is workspace-scoped in the Application service and SQL query. Draft and archived documents are not exposed as selectable published templates, even if their ID and revision are known. Version contracts remain immutable after a later template edit while the template stays published.

To check every design/template_render node in a workflow without changing it, call:

GET /api/v1/workflows/{wf_id}/template-revisions

This requires workflows:read scope plus workflow/template read permissions. The response gives the saved and latest revision, changed field codes and page counts, a compatibility reason, and the workflow definition ETag. latest_revision is null with an explanation when the source document is not currently Published; an editable draft is never treated as the latest published revision. get_workflow_template_revisions in MCP, workflow.review_template_revisions in AI Agent, and review_workflow_template_revisions in AI Assistant call the same Application review. Checking is read-only. To save a different pin, the author reviews the change and updates the workflow definition in an authoring surface; a Run never saves it.

Migration 480 adds template_version_policy to the renderer node. Its default pinned uses the saved revision. latest_compatible lets a new execution of the current workflow version use a newer published revision only when the existing field codes/types, required defaults, page count and repeated regions pass a conservative check. For a repeated region only data-facing changes need review: a removed or retyped item key, a new or newly required item key, a different list, overflow rule or lower Max items, or less room while the rule is to stop. Restyling, moving or resizing the row does not. Otherwise it keeps the saved pin and records the reason. The effective definition, including the choice or fallback, is written to a unique immutable execution snapshot and referenced by the WorkflowTask. Readback and retry use that snapshot. Historical version requests and released App bindings keep their frozen workflow version. AI Agent fixes the workflow version observed during planning; if it is still current when the new run starts, the node’s opt-in policy can resolve a newer template inside that run snapshot. If that workflow version became historical, the agent keeps its observed pin. This policy does not change the workflow’s saved template pin.

The existing REST PUT /api/v1/workflows/{wf_id}/definition with If-Match and its MCP/Agent draft authoring equivalents can persist a reviewed template_revision. The targeted command below updates selected pins under a workflow definition lock. REST, MCP, AI Agent and Editor authoring use that path; Assistant presents an explicit confirmation card in the Editor. Live qualification of these surfaces remains DD7 work.

At workflow publish, selecting the current revision or requesting an upgrade requires the template to be Published. A workflow that already holds an immutable template_revision pin can keep using that revision after the source template returns to draft or is archived. The internal workflow publisher checks that an existing pin still carries the same document/version identity and content/contract hashes. Supplying only legacy numeric document and version IDs after archive or revert cannot make a new template selection.

Draft inspection uses the separate design-templates:read scope and workspace WsAssetsRead permission:

GET /api/v1/design-templates/{tpl_id}/draft
GET /api/v1/design-templates/{tpl_id}/draft/publish-readiness
GET /api/v1/design-templates/{tpl_id}/draft/outline

The draft response gives its exact draft_etag (also in the ETag header), revision and status. The readiness response returns can_publish, blocking checks and suggested actions from the same server assessment used by the Editor. A later edit or publish must use the observed ETag as its precondition. This read surface returns no mutable full-document body. The outline lists page IDs, element IDs/names/types and named sample sets with their field codes, without exposing the editable JSON body. Use its page IDs when adding a local element. The first page can also be selected by omitting page_id.

list_design_templates returns the same list contract. get_design_template reads one template by view, each with the same projection as its REST v1 route:

view REST v1 equivalent Scope
contract (default; optional revision, else the current one) GET …/versions/{revision}/contract catalog:read
versions GET …/versions catalog:read
draft GET …/draft design-templates:read
outline GET …/draft/outline design-templates:read
content (optional revision, else the draft) GET …/draft/content, GET …/versions/{revision}/content design-templates:read
readiness GET …/draft/publish-readiness design-templates:read
palette (needs element_id) GET …/draft/elements/{element_id}/palette design-templates:read

All views also require WsAssetsRead. The contract carries the same portable field codes and hashes as REST v1, so an agent can wire design/template_render without guessing the JSON shape. The write scope design-templates:write also satisfies draft read, but it is not automatically granted to existing automation clients or default MCP consent; request it explicitly when authoring templates.

Use design-templates:write and workspace WsAssetsManage. A stable 8–255 character Idempotency-Key is required. Creation makes a native blank draft, never a published template:

POST /api/v1/design-templates
Idempotency-Key: proposal-template-001
Content-Type: application/json
{ "name": "Proposal template", "description": "Two-page proposal", "tags": ["proposal"] }

The response is 201 with the portable tpl_ ID inside draft.template.id, its draft_etag and an ETag header. Retrying with the same key and details returns that same template and Idempotency-Replayed: true; reusing the key with different details returns 409 idempotency_conflict. The workspace and key are persisted by migration 478. A draft whose metadata was saved just before an interrupted initial content upload is completed on retry.

MCP create_design_template_draft takes the same fields and required idempotency_key, calls the same Application service, and records a write audit with the template ID and hashed key.

The draft outline now reports each page’s one-based number, stable ID, name, format, orientation, dimensions, background color and element count. Integrations can build a brochure without downloading or replacing the DesignDocument JSON.

Every page mutation requires the latest If-Match and a fresh Idempotency-Key:

POST /api/v1/design-templates/{tpl_id}/draft/pages
POST /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/duplicate
PUT /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}
POST /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/move
DELETE /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}

For example, this inserts an A4 landscape page after the cover:

{
"name": "Services",
"format": "a4",
"orientation": "landscape",
"background_color": "#ffffff",
"after_page_id": "<cover page GUID>"
}

POST and PUT also accept optional background_paint for a full-page vector gradient. The shape is the same safe SVG paint object used by vector fills. For example, add this beside background_color:

"background_paint": {
"type": "linear_gradient", "units": "object_bounding_box",
"x1": 0, "y1": 0.5, "x2": 1, "y2": 0.5,
"stops": [
{ "offset": 0, "color": "#123456", "opacity": 1 },
{ "offset": 1, "color": "#abcdef", "opacity": 1 }
]
}

radial_gradient is also supported. Use 2–32 ordered stops with offsets and opacities between 0 and 1. background_color stays as the underlay for translucent stops. Omit background_paint on an update to keep the current gradient. Set clear_background_paint: true to remove it and return to the solid background_color. The draft outline returns the active background_paint, and the same input is available through MCP page tools and AI Agent page capabilities.

Add background_image as an optional PNG, JPEG or WebP layer above the solid color or gradient. Transparent pixels and uncovered page areas show the base:

"background_image": {
"src": "<uploaded image storage path or HTTPS URL>",
"fit_mode": "cover",
"focal_x": 0.75,
"focal_y": 0.4,
"scale": 1.25
}

cover fills the page and crops the image; contain shows the whole image; stretch fills without preserving aspect ratio. scale multiplies the fitted size (0.25–4, default 1). focal_x and focal_y align the scaled image within the page (0 = left/top, 1 = right/bottom, default 0.5). The editor lets the user drag and scale the image in a page preview. On PUT, omit background_image to retain it, supply a new image to replace it, or set clear_background_image: true to remove it independently of the base paint. The editor uploads a local image through the DesignDocument image upload endpoint. REST, MCP and AI Agent page commands accept the uploaded path or an HTTPS image URL. The draft outline includes the active image settings. Use a Madoo storage path for templates that will be shared: stored background assets are listed in published revisions and copied with shared templates. An external HTTPS URL can render, but it must be imported into Madoo storage before sharing the template.

Named formats are a3, a4, a5, letter, legal, tabloid and square. Use custom with width and height in PDF points for another size. A document can contain 1–100 authored pages. The last page cannot be removed. A page referenced as a repeating region’s continuation prototype is protected until that reference is changed. Duplicate creates fresh stable IDs for the page, every element, placeholder and guide; it never aliases the source objects. move accepts a one-based position.

The corresponding MCP tool is edit_design_template_page with operation add, duplicate, update or move; a page is removed with remove_design_template_item (kind page), the one destructive template edit.

AI Agent exposes the same operations as design_template.add_page, duplicate_page, update_page, move_page and remove_page. It reads the compact outline and chains the returned ETag after every local edit. AI Assistant remains workflow-scoped and does not edit DesignDocuments.

A master page holds a shared header, footer, logo, decoration or background. Create it from an existing page size, add static elements using its page_id, then assign it to one or more output pages of exactly that size:

POST /api/v1/design-templates/{tpl_id}/draft/master-pages
PUT /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}
PUT /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/master
PUT /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}/automatic-rule
POST /api/v1/design-templates/{tpl_id}/draft/pages/batch-master
DELETE /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}

Create with { "name": "Brochure header", "size_from_page_id": "<page GUID>" }. The creation response supplies the stable master page_id; use that ID to add text, shapes or images. Update its name, size or background with the same page-update body described above; changing its size is rejected while incompatible pages are attached. Assign with:

{
"master_page_id": "<master GUID>",
"use_master_background": true,
"master_layer": "underlay"
}

Use overlay to place master artwork above local elements. Set master_page_id to null to detach. Deleting a master detaches its pages; it does not delete their local content. The draft outline exposes master_pages, each output page’s master assignment and the master element IDs. Up to 32 masters can be stored in one document; they are not counted as output pages.

Set automatic_rule on a master to none, all, odd, or even when creating it, or update it later through /automatic-rule with { "automatic_rule": "odd" }. Only one master can use the same rule at a given page size; odd/even override all. New pages use automatic assignment. Existing pages retain their previous choice. The single-page /master body accepts assignment: automatic, manual, or none. A manual choice requires master_page_id; automatic and none require null. The latter two choices let a cover or back cover override the rules permanently. Automatic pages re-evaluate their master after reorder and on the final page number after repeated content expands.

To change several pages atomically, post { "scope": "all|odd|even|range", "start_page": 2, "end_page": 10, "assignment": "automatic|manual|none", "master_page_id": null, "use_master_background": true, "master_layer": "underlay" } to /batch-master. The inclusive range fields are needed only for range. A manual master must match every selected page’s size; otherwise nothing is saved. The draft outline returns automatic_rule on masters and master_assignment on output pages.

Master artwork can contain static elements and page-number fields. Put data placeholders, conditions and repeated regions on output pages. For editable page labels, author a normal text element whose text contains complete tokens, for example Page {{page}} of {{pages}}. {{page}} is the current page’s number within its numbering sequence; {{pages}} is the document page count; {{sequencePages}} is the current sequence count. Values use the active Arabic or Roman numbering style and resolve after repeated content expands. Literal words remain editable. A hidden page renders the entire token-bearing text element empty. Unknown or incomplete tokens stay literal. The editor inserts localized default words, while the stored text keeps the author’s wording. The same text works through REST v1, MCP and the AI Agent.

Existing documents can continue to use special_field_code set to page_number, page_label, document_page_count, sequence_page_count, page_document_slash, page_document_of, page_sequence_slash, or page_sequence_of. These field-only elements retain ordinary text styling and can live on a page or master. Their displayed text is resolved after repeat expansion. The draft outline exposes each element’s special_field_code. The labelled codes (page_label, page_document_of, page_sequence_of) always print Italian words (“Pagina 1 di 3”), whatever the document’s language, so new text should use the tokens above. REST v1 still accepts special_field_code when adding text, for older clients; the MCP tool and the AI Agent no longer offer it.

Use PUT /draft/pages/{page_id} with the existing page settings and optional numbering_start (1–3000) plus numbering_style (arabic, roman_upper, roman_lower) to restart a sequence on that page. clear_numbering_start removes the marker. hide_page_number hides page fields on one page without interrupting its sequence; a common use is an unnumbered cover followed by a second page starting at 1. The marker follows its page on reorder. The outline reports these page properties. Document page counts include hidden pages; sequence counts stop at the next marker. MCP add_design_template_text and edit_design_template_page (operation update), and AI Agent design_template.add_text and design_template.update_page, accept the same optional properties. For a local Development qualification after restarting the stack, run node docs/demo-workflows/design-template-render/qualify-page-fields-authoring-dev.mjs. The gate writes through REST and MCP, checks the outline and canonical content, then extracts the resulting page numbers from the PDF. To exercise the AI Agent planner as well, set MADOO_PAGE_FIELD_AGENT_GATE=true and run qualify-master-page-agent-dev.mjs from the same directory. Master assignments persist through published revisions, sharing/import and PDF rendering. These commands require the current If-Match draft ETag and a new Idempotency-Key for each edit.

MCP provides edit_design_template_master_page with operation create, assign, set_rule or batch_assign, and remove_design_template_item (kind master_page). AI Agent provides design_template.create_master_page, design_template.assign_master_page, design_template.set_master_rule, design_template.batch_assign_master and design_template.remove_master_page. For a local Development gate after restarting the stack, run node docs/demo-workflows/design-template-render/qualify-master-page-authoring-dev.mjs. It tests REST v1 and authenticated MCP writes, then revokes its temporary key and deletes the draft. install-master-page-brochure-dev.mjs installs the published brochure template and workflow; qualify-master-page-brochure-dev.mjs runs it and checks final page numbering in the PDF plus its page images. The checked-in brochure template demonstrates an all master rule, a fixed manual cover exception, and an automatic interior page. qualify-master-page-agent-dev.mjs runs a live AI Agent planner turn and checks that it authors one shared master and assigns it to two pages; this gate uses planner credits and deletes its temporary Idea and draft.

Add fixed artwork, vector lines and revise existing elements

Section titled “Add fixed artwork, vector lines and revise existing elements”

A fixed logo, photo, background or decorative asset is different from an image placeholder: its source is stored in the template and does not need render data.

POST /api/v1/design-templates/{tpl_id}/draft/static-images
If-Match: "dd-draft-r4-..."
Idempotency-Key: brochure-brand-art-v1
Content-Type: application/json
{
"page_id": "<cover page GUID>",
"name": "Brand artwork",
"source": "https://cdn.example.com/brand.png",
"left": 48, "top": 80, "width": 180, "height": 90,
"fit_mode": "fit"
}

source accepts an HTTPS URL, a Madoo storage path or an image data URI. HTTP URLs, absolute filesystem paths and control characters are rejected. Use fit, fill or stretch for sizing. To create an empty image frame, send "source":"" and "frame_shape":"rectangle", "ellipse", or a curated shape catalog ID. The frame stays editable while its image source is empty.

A divider, connector or arrow is a native vector line and remains vectorial in PDF:

POST /api/v1/design-templates/{tpl_id}/draft/lines
If-Match: "dd-draft-r5-..."
Idempotency-Key: brochure-arrow-v1
Content-Type: application/json
{
"page_id": "<page GUID>", "name": "Next step",
"x1": 80, "y1": 310, "x2": 420, "y2": 310,
"stroke_paint": { "type": "solid", "color": "#2563eb" },
"stroke_width": 3, "stroke_opacity": 0.85,
"stroke_line_cap": "round", "stroke_line_join": "round",
"stroke_dash_array": [12, 8],
"start_marker": "circle", "end_marker": "arrow"
}

Use an empty stroke_dash_array for a solid line. Common presets are [12, 8] for dashed and [1, 6] for dotted. Caps are butt, round, or square; endpoint decorations are none, arrow, or circle.

A rectangle starts with four sharp corners. Each corner can then be rounded independently:

POST /api/v1/design-templates/{tpl_id}/draft/rectangles
If-Match: "dd-draft-r6-..."
Idempotency-Key: brochure-panel-v1
Content-Type: application/json
{
"page_id": "<page GUID>", "name": "Feature panel",
"left": 80, "top": 120, "width": 280, "height": 160,
"corner_radius_top_left": 0,
"corner_radius_top_right": 24,
"corner_radius_bottom_right": 48,
"corner_radius_bottom_left": 12,
"fill_paint": { "type": "solid", "color": "#f8fafc" },
"stroke_paint": { "type": "solid", "color": "#334155" },
"stroke_width": 2
}

Corner radii use the document’s point coordinate system and must be between zero and half the shorter side. A zero remains a sharp corner. The same four optional fields are accepted by the element PATCH, so integrations can change one corner without replacing the others. MCP exposes add_design_template_rectangle; AI Agent exposes design_template.add_rectangle. The Editor’s live-corner widgets write the same fields.

Madoo exposes a versioned catalog of built-in vector shapes. Read the catalog before authoring so the integration does not have to guess IDs or embed its own geometry:

GET /api/v1/design-template-shapes

The response groups shapes into basic, arrows, stars_badges, callouts, and flowchart. Each item includes a stable shape_id, display name, search keywords, standard SVG path_data, a normalized view box, fill rule, provenance, and license. Catalog version 1.0.0 contains 25 Madoo-original shapes.

Add a selected shape to the draft with the latest ETag and a fresh retry key:

POST /api/v1/design-templates/{tpl_id}/draft/shapes
If-Match: "dd-draft-r6-..."
Idempotency-Key: brochure-feature-star-v1
Content-Type: application/json
{
"page_id": "<page GUID>",
"shape_id": "star_5",
"left": 80, "top": 120, "width": 120, "height": 120,
"fill_paint": { "type": "solid", "color": "#f97316" },
"stroke_paint": { "type": "solid", "color": "#7c2d12" },
"stroke_width": 2
}

The operation copies the standard SVG path into a normal PathElement. The saved document, its revisions, shared imports, and PDFs therefore remain independent of later catalog changes. MCP exposes list_design_template_shapes and add_design_template_shape; AI Agent exposes design_template.list_shapes and design_template.add_shape. Editor, REST v1, MCP, and AI Agent all use the same catalog and canonical vector representation.

Custom vector artwork uses the standard SVG d syntax rather than Fabric JSON or a Madoo-specific path language:

POST /api/v1/design-templates/{tpl_id}/draft/svg-paths
If-Match: "dd-draft-r6-..."
Idempotency-Key: brochure-feature-star-v1
Content-Type: application/json
{
"page_id": "<page GUID>", "name": "Feature star",
"path_data": "M 60 0 L 74 42 L 120 42 L 82 68 L 96 112 L 60 84 L 24 112 L 38 68 L 0 42 L 46 42 Z",
"left": 80, "top": 120, "width": 120, "height": 112,
"fill_paint": {
"type": "linear_gradient", "units": "object_bounding_box", "spread_method": "pad",
"x1": 0, "y1": 0, "x2": 1, "y2": 1,
"gradient_transform": [],
"stops": [
{ "offset": 0, "color": "#f97316", "opacity": 1 },
{ "offset": 1, "color": "#7c3aed", "opacity": 1 }
]
},
"fill_rule": "non_zero"
}

The bounded safe profile accepts SVG commands M/L/H/V/C/S/Q/T/A/Z, including relative commands. Fill and stroke paint can be none, solid, linear_gradient, or radial_gradient, with 2–32 ordered stops and an optional six-value SVG gradient_transform. Gradient stroke stops must be opaque; fill-stop transparency is preserved in the vector PDF through a soft mask. paint_order chooses fill_stroke or stroke_fill; non_scaling_stroke keeps the visual stroke width during proportional resizing. Unsupported combinations fail validation instead of producing a visually different PDF.

Every gradient stop accepts an exact offset from 0 to 1 and a #RRGGBB color. Fill stops additionally accept opacity from 0 to 1. Linear geometry uses x1, y1, x2 and y2; radial geometry uses cx, cy, r and optional fx, fy, fr. Coordinates are fractions when units is object_bounding_box and local document units when it is user_space_on_use. The Editor exposes the same data as draggable stops, exact color, position and opacity fields, linear angle, and radial center, focus and radius controls. Changing gradient kind preserves existing stops and any imported gradient_transform.

Complete SVG clipart or multi-path artwork can be imported in one operation:

POST /api/v1/design-templates/{tpl_id}/draft/svg-imports
If-Match: "dd-draft-r7-..."
Idempotency-Key: brochure-brand-clipart-v1
Content-Type: application/json
{
"page_id": "<page GUID>",
"name": "Brand clipart",
"svg": "<svg viewBox=\"0 0 200 100\"><path d=\"M0 0H200V100H0Z\" fill=\"#2563eb\"/></svg>",
"left": 80, "top": 120, "width": 240, "height": 120,
"mode": "preserved"
}

mode accepts editable (the default) or preserved. Editable import converts the supported SVG subset into normal Madoo groups, paths and text for deep editing. Preserved import keeps complex passive artwork as one atomic vector element. Its source is an immutable managed asset and its palette remains editable and resettable. Both modes reject active and external content and neither silently rasterizes the file.

The importer accepts at most 10 MB of UTF-8 SVG, 500 drawable objects and 16 levels of source nesting. It converts paths, rectangles, circles, ellipses, lines, polylines, polygons and groups into normal canonical groups and paths. It supports local paints, gradient transforms, fill and clip rules, gradient stroke, paint order, non-scaling stroke, and affine transform matrices including skew. A bounded embedded-CSS subset supports simple element, .class, #id, and compound selectors for the same portable visual properties; inline style keeps normal cascade precedence. CSS imports, at-rules, combinators, pseudo-classes, external URLs, and unsupported visual properties are rejected. Local href/xlink:href inheritance between gradient definitions and bounded local use references are resolved before conversion. Vector clip paths, bounded alpha or luminance masks, endpoint markers, and simple single-run SVG text become canonical DesignDocument data. Non-rendering editor metadata is ignored. External legacy DOCTYPE declarations are ignored with resolution disabled; DTD entities remain invalid. The source XML and CSS are discarded after conversion. Scripts, event handlers, external references, embedded SVG images, arbitrary filters, marker-mid, tspan, text paths and other unsupported constructs fail with a specific svg_import_* error. They are never silently rasterized or simplified.

Portable colors include named colors, #RGB, #RRGGBB, integer rgb(0, 128, 255) and decimal percentage rgb(0%, 50.2%, 100%) notation. Percentage channels must remain between 0 and 100 and are normalized to #RRGGBB; paint opacity stays in its explicit SVG opacity field.

MCP exposes import_design_template_svg; AI Agent exposes design_template.import_svg. Both accept the same mode. The Editor asks which mode to use when an SVG is selected through the Image tool. Revisions and sharing carry either canonical geometry or the immutable preserved asset. Both PDF paths remain vectorial.

Preserved mode accepts at most 10 MB of UTF-8 SVG, 2,000 elements and 32 levels of nesting. It flattens the safe CSS subset, rejects scripts, event handlers, external files, network references and embedded raster data, and runs the actual PDF graphics preflight before storing the content-addressed asset. A saved svg_artwork records the source hash, sanitizer profile, viewBox and bounded paint-token palette. Palette changes modify only the token overrides; they do not rewrite the source geometry.

An imported canonical artwork keeps an editable palette after import. Selecting its group in the Editor opens Colors, which lists each original color, its current value and its use count. A color can be changed throughout the artwork and later restored on its own; Reset all restores the complete import-time palette. The replacement covers solid fills, outlines, gradient stops, simple SVG text and endpoint decorations while preserving geometry, opacity, gradient coordinates and transforms. It is a normal draft edit, so it survives save/reopen, revisions, sharing and PDF rendering.

The original value is stored beside every canonical paint occurrence rather than inferred from the current palette. If two original colors are both changed to the same target, either one can therefore still be reset independently. Documents created before this metadata existed capture their current palette as the baseline on their first palette edit.

REST v1 exposes the same semantic operation without requiring the full document body:

GET /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palette
POST /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palette-replacements
If-Match: "dd-draft-r8-..."
Idempotency-Key: recolor-brand-artwork-001
Content-Type: application/json
{ "source_color": "#f97316", "target_color": "#22c55e" }

source_color identifies the immutable original palette token returned by the read. The read response returns original_color, current color, uses, and modified for every editable paint in the selected element subtree. Restore one token, or omit original_color to restore the complete palette:

POST /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palette-resets
If-Match: "dd-draft-r9-..."
Idempotency-Key: reset-brand-artwork-001
Content-Type: application/json
{ "original_color": "#f97316" }

MCP reads the palette with get_design_template (view palette, element_id) and changes it with edit_design_template_palette (operation replace_color, reset_color or reset_all); AI Agent exposes design_template.get_element_palette, design_template.replace_palette_color, design_template.reset_palette_color, and design_template.reset_palette. All authoring surfaces call the same bounded Application palette logic.

Rectangle, circle, ellipse and SVG path use the same portable fill contract. Their fill_paint can be none, solid, linear_gradient or radial_gradient, with a separate fill_opacity. Rectangle, circle, ellipse, line and SVG path also use the same stroke fields: stroke_paint (none, solid, linear_gradient, or radial_gradient), stroke_width, stroke_opacity, stroke_line_cap, stroke_line_join, stroke_dash_array, stroke_dash_offset and stroke_miter_limit and non_scaling_stroke. Closed vectors also accept paint_order. The earlier fill and stroke color strings remain accepted when typed paint is absent, so previously saved templates do not need a migration.

Existing elements can be changed or removed locally:

PATCH /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}
DELETE /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}

The patch accepts common geometry and state fields (name, left, top, width, height, angle, opacity, visible, locked, flip_x, flip_y). The flip fields mirror an element within its authored bounding box and are preserved in editor, preview, rendered pages and PDF. target_page_id moves a top-level element to another page. Text elements additionally accept text, font family/size/ weight/style, color, alignment, line height, underline, strikethrough and bounded overflow settings. Image elements accept image_source and fit_mode. A vector image frame uses frame_shape (rectangle, ellipse, or a shape catalog ID) or frame_path_data (a closed standard SVG path in image-local PDF points). Optional frame_focal_x, frame_focal_y (0–1), frame_scale (0.01–4), frame_offset_x, frame_offset_y (image-local points, bounded to four times the image size), and frame_rotation (degrees, -360 to 360) place the source independently inside the clipped frame. The offset remains effective even when the source and contour have identical dimensions. The editor may lower the stored frame scale while enlarging the mask viewport so the photo keeps the same visible size and page position. frame_stroke_color and frame_stroke_width draw an inside border. clear_frame releases the frame. mask_shape or mask_path_data adds a vector opacity mask in the same local coordinate system, with optional mask_opacity (0–1); clear_mask removes it. The image frame and mask travel with the normal element through revisions, published versions, sharing/import, preview and PDF. Closed vector elements accept the shared fill fields, while every vector element accepts the shared stroke fields; lines additionally accept start/end decorations. Rectangles accept corner_radius_top_left, corner_radius_top_right, corner_radius_bottom_right, and corner_radius_bottom_left. Every drawable element accepts a shadows array containing zero or one outer shadow; semantic containers and groups do not. The shadow has color, opacity, offset_x, offset_y, blur, spread, and non_scaling; send an empty array to remove it. Coordinates and sizes use the same PDF-point space as the element, and the shadow is retained by draft revisions, published versions, sharing/import, page rendering and PDF export. Type-specific fields on the wrong element type fail before saving. An SVG path additionally accepts path_data on the same update operation, using the standard safe d grammar (M/L/H/V/C/S/Q/T/A/Z, including relative forms). This lets REST, MCP and AI Agent reshape artwork created in the Editor without replacing the document. Nested elements can be edited or removed in place; moving one across pages requires moving its containing region.

MCP uses add_design_template_static_image, add_design_template_line, add_design_template_rectangle, add_design_template_svg_path, import_design_template_svg, update_design_template_element and remove_design_template_item (kind element). AI Agent uses the parallel design_template.add_static_image, add_line, add_rectangle, add_svg_path, import_svg, update_element, update_image and remove_element capabilities. Its text/common update and image-specific update are separate so the planner cannot accidentally apply image defaults to a text box. All three surfaces call the same Application commands, workspace checks, ETag compare, transactional receipt and content validator.

Use design-templates:write and WsAssetsManage. Read the outline for the current draft_etag and an optional page ID, then add a named text element using PDF points. A blank A4 draft is about 595 × 842 points. This command modifies just the requested element; it does not accept a replacement document body.

POST /api/v1/design-templates/{tpl_id}/draft/text-elements
If-Match: "dd-draft-r1-..."
Idempotency-Key: proposal-title-001
Content-Type: application/json
{ "name": "Proposal title", "text": "Proposal for Acme", "left": 36,
"top": 42, "width": 500, "height": 60 }

The response returns element_id, the new draft_revision and draft_etag. A stale ETag returns 412 precondition_failed. Retrying the same body/key returns the original result and Idempotency-Replayed: true even when the draft has since advanced; changed body with the same key returns 409 idempotency_conflict. The semantic body, not the If-Match header, defines the retry payload. Migration 479 stores the receipt in the same SQL transaction as the draft revision and placeholder index. The Editor continues to use its existing DesignDocument content save path and sees this added element on reload.

MCP add_design_template_text uses the same command and contract. It requires if_match and idempotency_key, applies the same workspace/scope/permission guard and records a write audit.

POST /draft/text-elements and PATCH /draft/elements/{element_id} accept optional rich_text in the renderer-neutral madoo.rich-text/v1 format. It contains paragraphs and contiguous character runs; it never contains HTML, editor selection offsets or Fabric objects. Each null run property inherits from the owning text element.

{
"name": "Proposal title",
"left": 36, "top": 42, "width": 500, "height": 80,
"rich_text": {
"schema": "madoo.rich-text/v1",
"paragraphs": [
{ "list": { "kind": "ordered", "level": 0 }, "runs": [
{ "text": "Proposal for ", "fontWeight": "bold" },
{ "text": "Acme", "fontStyle": "italic", "fill": "#2563eb" }
] },
{ "list": { "kind": "ordered", "level": 0 }, "runs": [ { "text": "Hackathon brief", "underline": true } ] }
]
}
}

The Application service derives the compatible TextElement.Text projection by joining runs and paragraphs with \n. When a client also supplies text, it must equal that projection. An LF inside a run is a soft break belonging to the same paragraph/list item (the editor authors it with Shift+Enter); a paragraph boundary is a hard break and starts the next item. CR characters are invalid. The bounded format allows 1–1000 paragraphs, at least one run per paragraph, at most 10,000 runs and 10,000 projected characters. A paragraph can carry optional list semantics: "list":{"kind":"bullet|ordered","level":0,"start":1}. level is 0-8; start is allowed only on an ordered paragraph and restarts that level between 1 and 1,000,000. Markers are generated by the editor and PDF renderer and never become part of TextElement.Text, run text, selection offsets or placeholder values. A run can override fontFamily, fontSize, fontWeight, fontStyle, exact portable font, fill, underline, linethrough and baselineShift (positive values move upward). Rich text upgrades the draft to DesignDocument schema 2.0 and participates in revisions, sharing/import, font manifests, masters/components, previews and PDF rendering.

On PATCH, sending plain text deliberately clears prior inline formatting. Send clear_rich_text: true to remove inline formatting while retaining the existing plain projection. rich_text and clear_rich_text cannot be combined. MCP add_design_template_text and update_design_template_element, plus Agent design_template.add_text and design_template.update_element, use the same canonical object, validation, ETag and idempotency rules. Lists are paragraph semantics; columns will belong to the frame; linked frames will reference one shared text story rather than duplicate the run content.

To make that text dynamic, read its element_id from the outline and configure a text field:

PUT /api/v1/design-templates/{tpl_id}/draft/text-elements/{element_id}/placeholder
If-Match: "dd-draft-r2-..."
Idempotency-Key: proposal-headline-field-001
Content-Type: application/json
{ "code": "headline", "name": "Proposal headline", "required": true,
"description": "The title supplied at render time" }

This creates or updates the placeholder on just that element. The code becomes a key in the published revision’s fields contract and in the design/template_render JSON input, for example { "headline": "Proposal for Acme" }. Use a new retry key for this command. The same ETag/retry and workspace rules apply; the Editor sees the binding on reload. MCP configure_design_template_element_placeholder with type text uses the same service and response.

For an element already present in a native draft, the typed variant can bind any compatible field. Read the outline for its element ID and current ETag, then use a fresh retry key:

PUT /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/placeholder
If-Match: "dd-draft-r5-..."
Idempotency-Key: proposal-budget-field-001
Content-Type: application/json
{ "type": "number", "code": "budget", "name": "Campaign budget",
"required": true, "description": "Amount supplied as a JSON number" }

The field types follow the DesignDocument renderer’s compatibility rules:

Field type Compatible existing element Example values JSON
text, number Text "headline": "Proposal", "budget": 1200
boolean Text or container "approved": true
image Image "hero": "https://example.com/hero.jpg"
color Text or compatible shape "accent": "#2255AA"
json Repeat region "items": [{"name":"A"}]

A repeat region’s JSON source code is updated with the field code. An incompatible element/type, invalid code or wrong-type default returns 400 before a draft write. The same ETag/retry behavior applies as for text fields. MCP configure_design_template_element_placeholder calls the same command and returns the same result. This command binds existing elements.

One command places an image box and defines its required image field together:

POST /api/v1/design-templates/{tpl_id}/draft/image-placeholders
If-Match: "dd-draft-r3-..."
Idempotency-Key: proposal-hero-image-001
Content-Type: application/json
{"name":"Hero image","code":"hero_image","left":48,"top":80,
"width":495,"height":260,"fit_mode":"fill"}

Dimensions are PDF points within the selected page; page_id is optional and defaults to the first page. fit shows the whole image, fill crops it to cover the box, and stretch changes its proportions. The field code becomes a key in sample values and design/template_render.data, for example {"hero_image":"data:image/png;base64,..."} or an image storage URI. The placeholder is required, so add a sample image and inspect its exact preview before publish. MCP add_design_template_image_placeholder calls the same Application command with the same permission, ETag and retry receipt.

This one command creates a native repeat region, its JSON array field and a text row bound to one property of each item. It edits only that region; no full DesignDocument JSON is sent to the API:

POST /api/v1/design-templates/{tpl_id}/draft/repeating-text-lists
If-Match: "dd-draft-r3-..."
Idempotency-Key: proposal-items-list-001
Content-Type: application/json
{"name":"Proposal line items","source_code":"items","item_field":"name",
"left":48,"top":120,"width":495,"height":240,"item_height":32,
"max_items":10,"overflow_policy":"fail"}

source_code is the JSON key supplied to design/template_render.data; item_field is the property read from each array item. For example, {"items":[{"name":"Discovery"},{"name":"Delivery"}]} draws two rows. The list area and row height use PDF points. max_items bounds expansion and overflow_policy chooses fail, fit, clip or continue_page when rows do not fit (continue_page adds copies of the page; only one such list per page, placed directly on it). The design/template_render node keeps this rule by default (overflow_policy template); setting the node to fail, fit or clip applies that rule to every Repeat of the template instead. Read the outline for its page ID and current ETag, then preview a sample before publishing. MCP add_design_template_repeating_text_list uses the same Application command, permission, retry receipt and response. The Development qualifier qualify-dd7-repeating-list-dev.mjs checks two rendered rows and a page JPEG; repeating-line-items.json is the workflow example with PDF, page image and layout report.

Rows with several fields: cards, tables, galleries

Section titled “Rows with several fields: cards, tables, galleries”

A row is not limited to one text: like in the editor it can hold texts, images, shapes and Layouts — a product card with photo, name, price and a “sold out” badge. Build it with the same commands used on a page, adding parent_id:

  1. Add elements into the row. Every add command (text-elements, image-placeholders, rectangles, lines, shapes, svg-paths, svg-imports, static-images) accepts "parent_id": "<repeat_region id>"; coordinates are then relative to the row. parent_id also places an element inside a layout_region, a group or a layer folder.
  2. Bind them to the keys of each item. A field bound inside a row — with PUT /draft/text-elements/{id}/placeholder, PUT /draft/elements/{id}/placeholder or an image box added with parent_id — is a key of each list item: the outline shows binding_path: "item.price" and the data is {"products":[{"name":"Lampada","price":89,"photo":"…","sold_out":false}]}. A number key keeps its format.
  3. Arrange the row with POST /draft/layouts/arrange (the elements share the row as parent), and set what sits on top with the element update’s z_order (front, back, forward, backward): a card background added after its texts goes back.
  4. Show an element only when a key says so with the element update’s condition: {"condition":{"operator":"equals","placeholder_code":"item.sold_out","literal":"true"}}. Operators: not_empty, equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal (numeric literal), not (one condition), all / any (conditions); a code outside a row is a template field. clear_condition removes it.
  5. Configure the Repeat — the editor’s Repeat panel:
PATCH /api/v1/design-templates/{tpl_id}/draft/repeats/{repeat_id}
If-Match: "dd-draft-r7-..."
Idempotency-Key: catalog-grid-001
{"list_key":"products","max_items":9,"overflow_policy":"continue_page",
"layout":{"mode":"grid","columns":3,"row_gap":16,"column_gap":12},
"item_fields":[{"code":"sold_out","name":"Sold out","type":"boolean"}]}

list_key renames the list everywhere it is named (its field, conditions, sample sets); layout lays the rows out (vertical, horizontal, grid with columns, gaps, padding, alignments); item_fields declares the keys a condition reads but the row does not print. MCP configure_design_template_repeat and the agent’s design_template.configure_repeat run the same command. The outline reports a Repeat’s list_key, max_items, overflow_policy, item_fields, and every element’s position, size, placeholder_type, binding_path, format and condition.

Two more element properties, on the same PATCH /draft/elements/{element_id} (MCP update_design_template_element, agent design_template.update_element):

  • char_spacing — letter spacing of a text, in thousandths of an em (100 = a tenth of the font size; 300 for a spaced-out small-caps label).
  • follows_row_height — in a Repeat row whose texts grow: true makes a rectangle, ellipse, image or line stretch with the row (a card background), false keeps its size; clear_follows_row_height returns to the automatic rule (full-height rectangles and lines stretch).

Whole documents: read, write, create and duplicate

Section titled “Whole documents: read, write, create and duplicate”

Everything above edits a draft one command at a time. A template is also one JSON document, in the public format madoo.design-document/2.0 — the format the editor saves, so everything the editor can author can be written here. Its JSON Schema is served by the schema catalog: GET /api/v1/json-schemas/madoo.design-document/2.0 (MCP get_json_schema).

GET /api/v1/design-templates/{tpl_id}/draft/content # the draft; ETag header
GET /api/v1/design-templates/{tpl_id}/versions/{revision}/content # a published revision, as published
PUT /api/v1/design-templates/{tpl_id}/draft/content # If-Match + Idempotency-Key; body = the document
POST /api/v1/design-templates # {"name":"…","content":{…}} starts the draft as that document
POST /api/v1/design-templates/{tpl_id}/duplicate # {"name":"…","revision":2} copies a revision (or the draft)
  • The format. schemaVersion is "2.0". Pages, master pages, components and container children hold elements discriminated by $type: text, image, rectangle, circle, ellipse, line, path, group, layout_region, repeat_region, svg_artwork, component_instance, folder. IDs are GUIDs unique in the document; a field is a placeholder on its element (code, placeholderType, bindingPath item.key inside a Repeat row, format). Read an existing template to see a complete example: it is the quickest way to learn the format.
  • Validation. The document goes through the editor’s validation and save. Unknown properties are errors (a misspelt name would otherwise be lost), as are duplicate IDs, invalid field codes, bindings, conditions and limits. A rejected document returns 422 content_invalid with every problem in errors (field = JSON path, message = code and explanation).
  • Concurrency and retries. PUT requires the draft ETag as If-Match and an Idempotency-Key, like every draft edit; POST requires an Idempotency-Key.
  • When to use it. Build or rewrite a whole template, copy an example and adapt it, move a template between workspaces (fonts and storage images must exist in the target workspace). For small changes the element commands above are safer: they cannot touch what they do not name.

Sample sets. A document sent without a sampleSets property keeps the draft’s sample sets (an empty list clears them). REST v1 returns them in full; MCP (get_design_template view content) and the agent (design_template.get_content) leave them out unless include_sample_sets is true and list them in omitted_sample_sets (ID, name, field codes): they are example values, often with inline images, and can weigh most of a template (F05: 180,000 of 255,000 characters). Reading the draft without them and sending it back therefore never loses them.

One page of the outline. MCP get_design_template view outline and the agent’s design_template.get_draft_outline take page_id (a page or master page) to list only that page’s elements; every page, master page, sample set, style and component stays listed.

MCP: get_design_template (view content), replace_design_template_content, duplicate_design_template, create_design_template_draft with content. Agent: design_template.get_content, design_template.replace_content, design_template.duplicate, design_template.create_draft with content. The template list (GET /api/v1/design-templates, MCP list_design_templates, agent design_template.list) takes status = published (default), draft, archived or all.

Read the current draft outline for its ETag and the placeholder codes, then add a named sample set. Its values object uses those codes as keys, exactly like the design/template_render data input. For a required text field called headline:

POST /api/v1/design-templates/{tpl_id}/draft/sample-sets
If-Match: "dd-draft-r3-..."
Idempotency-Key: proposal-headline-sample-001
Content-Type: application/json
{ "name": "Proposal authoring example",
"values": { "headline": "Proposal for Acme Studio" } }

The response supplies sample_set_id and the new draft ETag. The outline lists the sample by name and covered field codes. Unknown codes or values of the wrong JSON type return 400. A stale ETag returns 412; the same body/key replays without another draft revision, while a changed body with that key returns 409. Use a different retry key for each sample. MCP edit_design_template_sample_set (operation add) uses the same Application command, scope and audit rules.

When a new required field is added later, an earlier sample may become unready. Extend that specific sample without replacing the document or losing its other values:

PATCH /api/v1/design-templates/{tpl_id}/draft/sample-sets/{sample_set_id}/values
If-Match: "dd-draft-r6-..."
Idempotency-Key: proposal-example-add-budget-001
Content-Type: application/json
{ "values": { "budget": 1200 } }

This merges just budget into the selected sample. Read its ID and ETag from the outline. Unknown codes or wrong JSON types return 400; a missing sample returns 404, stale ETag 412, same-key replay returns the original revision, and changed payload/key returns 409. MCP edit_design_template_sample_set (operation set_values) uses the same command and write audit. Existing values such as headline remain in the sample; the next readiness check evaluates all samples again.

An exact preview is a read operation using design-templates:read and WsAssetsRead:

GET /api/v1/design-templates/{tpl_id}/draft/sample-sets/{sample_set_id}/exact-preview?max_page_dimension=900

It returns page JPEG data_uri values, a compact layout report and readability/readiness checks for the current draft. The page size can be 400–1600 pixels. MCP preview_design_template_sample_set returns the same projection. Preview renders the sample with the DesignDocument engine and makes no AI provider call. Check the pages and fix blocking diagnostics before publication. The readiness response reports whether required fields remain unexercised by any sample.

The default workflow pin stays at the published revision selected during authoring. The latest_compatible policy may use a newer compatible revision in one new run, but that run never edits the saved workflow. To promote a saved pin, review the affected workflow first:

GET /api/v1/workflows/{wf_id}/template-revisions

This response shows each renderer’s saved/latest revision, field and page changes, compatibility issue and the definition ETag. Choose only nodes marked compatible. Then explicitly save the chosen revision with the ETag from that review:

POST /api/v1/workflows/{wf_id}/template-revisions/upgrade
If-Match: "<definition ETag from review>"
Content-Type: application/json
{"nodes":[{"node_id":"render_proposal","target_revision":2}]}

One request may select 1–20 distinct nodes. The command changes only their published template pins, preserves connections and editor layout, validates the whole resulting workflow and returns the new workflow version/ETag. A Published workflow gets a new copy-on-write definition version; an Archived workflow cannot be edited. An old ETag returns 412; an unreviewed or incompatible revision returns 409. Review again after either conflict. MCP get_workflow_template_revisions and upgrade_workflow_template_revision provide the same read/write sequence for one node, and AI Agent exposes workflow.review_template_revisions followed by workflow.upgrade_template_revision. The Editor offers a local revision diff before the author saves its canvas. When AI Assistant requests the same read-only review, its chat panel shows a card with a fresh server review, one-node selection and confirmation. Only the author’s click invokes the REST write with ETag; the card requires the reviewed workflow to be open in Editor with no unsaved changes.

The Development example DD7 Persistent Pin Upgrade — Isolated Proposal uses the compatible two-revision fixture and checks this operation without creating an execution or changing the original two-page proposal workflow.

Render a published revision directly as PDF

Section titled “Render a published revision directly as PDF”

DD9 applies shared admission to direct PDF render, exact preview and workflow render. A request exceeding configured data-set, aggregate output-page or resolved-element limits fails with DESIGN_RENDER_LIMIT_EXCEEDED; an oversized image fails with DESIGN_IMAGE_LIMIT_EXCEEDED, an excessive total of materialized images with DESIGN_IMAGE_TOTAL_LIMIT_EXCEEDED, an image exceeding the pixel budget with DESIGN_IMAGE_PIXEL_LIMIT_EXCEEDED, an oversized native content JSON with DESIGN_CONTENT_INPUT_LIMIT_EXCEEDED, and an oversized imported PDF with DESIGN_PDF_INPUT_LIMIT_EXCEEDED. A native draft whose stored JSON differs from its recorded SHA-256 fails with CONTENT_HASH_MISMATCH; a published version uses VERSION_HASH_MISMATCH. PDF sections already exceeding the output budget are rejected before rendering; a final PDF exceeding it fails with DESIGN_PDF_OUTPUT_LIMIT_EXCEEDED. The limits are deployment configuration (DesignTemplates:RenderLimits), not client-supplied fields. Initial defaults are 100 data sets, 200 output pages, 25,000 resolved elements, 20 MiB per image, 100 MiB total images, 40 million pixels per image, 32 MiB per native content JSON, 100 MiB per imported PDF and 100 MiB per output PDF. The final-byte cap is checked after PDF generation; file-backed native outputs are deleted on rejection.

The resolver checks workspace access before every cache lookup. Verified, content-addressed JSON is cached per organization, workspace, path and SHA-256 with a 128 MiB process-local budget and 10-minute absolute TTL by default. Content without a recorded hash is read within the byte limit but is not cached. Concurrent requests for the same content share one download per process; a hash mismatch is rejected and never cached.

POST /api/design-documents/import-pdf applies MaxImportedPdfBytes before multipart buffering and PDF analysis. The default is 100 MiB; an oversized upload returns HTTP 413 with DESIGN_PDF_INPUT_LIMIT_EXCEEDED and creates no document. The request body may use up to 1 MiB extra for the multipart envelope.

For imported PDFs with multiple data sets, the renderer also stops before merge when the cumulative byte size of generated iterations exceeds MaxOutputPdfBytes. This is a conservative bound: it may reject an input whose merged PDF would have deduplicated some bytes. The error remains DESIGN_PDF_OUTPUT_LIMIT_EXCEEDED.

DD9 render admission also has process-local pools: two simultaneous interactive preview/direct export renders and four workflow renders by default. A slot covers the PDF render and every page image rasterized from it (exact preview, pages/image outputs, REST v1 and MCP page renders). Requests beyond the slots wait in a bounded first-in-first-out queue (32 interactive, 64 workflow) for at most 20 seconds (interactive) or 60 seconds (workflow). When the queue is full or the wait expires the caller receives DESIGN_RENDER_BUSY: HTTP 503 with Retry-After: 5 on direct REST routes (REST v1 included), a retryable node failure in workflows. Cancellation while waiting aborts immediately. Exporting an imported PDF that has no image replacement returns the unchanged original without taking a slot. All values are configurable under DesignTemplates:RenderLimits.

The editor’s PDF export and the design/template_render node can produce an ordinary PDF or a print-ready PDF/X-4 that embeds an ICC profile uploaded by the user as its OutputIntent. The catalog belongs to the workspace and can hold several profiles:

GET /api/v1/print-color-profiles

The upload is multipart and requires a printer-class CMYK ICC profile, version 2 or 4, of at most 5 MB:

POST /api/v1/print-color-profiles
Content-Type: multipart/form-data
displayName=PSO Coated v3 — Printer A
outputConditionIdentifier=FOGRA51
setAsDefault=true
licenseAcknowledged=true
file=@printer-a.icc

The file is immutable and identified by profileGuid and sha256. Change the default with POST /api/v1/print-color-profiles/{profileGuid}/default, or archive a profile with POST /api/v1/print-color-profiles/{profileGuid}/archive. Archiving removes it from new selections but does not invalidate published workflows that already pinned it.

The editor endpoint POST /api/design-documents/{id}/generate-pdf accepts, besides the placeholder values:

{"exportMode":"pdfx4","colorProfileGuid":"11111111-1111-4111-8111-111111111111"}

exportMode is standard by default. On the design/template_render node the equivalents are pdf_export_mode=pdfx4 and color_profile_guid; on publish Madoo adds the private fingerprint colorProfileSha256. The layout_report records the mode and the identity of the profile used.

This first version keeps the authored colours as managed RGB (sRGB by default) and uses the CMYK profile as the print destination (output intent). It offers no soft proof, spot colours, overprint, trapping, total-ink control or per-image source conversion. Imported PDFs and intro/outro PDFs cannot take the PDF/X-4 path; use standard or a native DesignDocument. The profile must match the printer’s specifications, and a clone in another workspace must select or upload an authorised profile there.

Read the published revision contract for its exact field codes and JSON types. Send that JSON object as the request body:

POST /api/v1/design-templates/{tpl_id}/versions/{revision}/render-pdf
Content-Type: application/json
{"customer":"Example Customer","headline":"Example Proposal"}

The response is a PDF download and carries X-Design-Template-Version: tplv_... and X-Design-Template-Pages. This read-only render uses the same production template renderer as the workflow node. Unknown codes, wrong JSON types, duplicate codes and missing required fields fail before rendering; the body is limited to 1 MB/200 fields and the direct result to 20 MB/100 pages. Use a workflow for larger or repeated production outputs. MCP render_design_template accepts template_id, revision, data_json and output pdf and returns the PDF as an embedded binary resource with small metadata. Neither REST nor MCP stores a new asset or calls an AI provider. The internal AI Agent design_template.render_pdf uses the same revision and typed data contract, stores the resulting PDF as an Idea-linked workspace asset and returns only its opaque reference, file metadata and digest to the planner. The conversation shows the generated file with a browser-resolved download; PDF bytes and private storage paths never enter model context. AI Assistant check_design_template_pdf_render remains a workflow-authoring check and returns only page count, byte count, digest or field errors.

Render selected published pages and inspect the manifest

Section titled “Render selected published pages and inspect the manifest”

Use the same typed JSON object with render-pages when an integration needs page images directly, without first creating a workflow execution:

POST /api/v1/design-templates/{tpl_id}/versions/{revision}/render-pages?page_selection=1,3-4&max_page_dimension=1200
Content-Type: application/json
{"customer":"Example Customer","headline":"Example Proposal"}

page_selection accepts all, individual one-based page numbers and ranges. The response identifies the immutable tplv_ revision, total rendered pages, selected pages and one JPEG item per page with byte count, SHA-256 and a data_uri. The longest image side can be 400–1600 pixels. A direct request is limited to 20 selected pages and 20 MB of JPEG data; use a workflow when images must be durable, reused, or larger. Invalid ranges return an RFC 7807 error that states the valid rendered page interval.

MCP render_design_template with output pages accepts the same template_id, revision, data_json, selection and dimension. It returns the compact manifest as structured content and attaches each JPEG as an embedded binary resource, so base64 data is not copied into the explanatory text. REST and MCP call the same Application service and PDF rasterizer as design/template_render. They store no asset, create no execution and call no AI provider.

A published template always keeps an editable draft: you do not need to return it to draft to prepare the next revision. Edit the draft (element commands, or read and PUT the whole document), check it with a sample set and the publish readiness, then publish: the result is a new immutable revision (revision 2, 3, …). While you work:

  • workflows whose design/template_render node is pinned to an earlier revision keep rendering it; review and move their pin with the template revisions endpoints;
  • document/pdf and aggregate/pdf nodes without template_revision render the draft at their next run, including unfinished edits; nodes with template_revision keep their revision.

revert-to-draft is for a different purpose: it takes the template out of selection (status draft) while it is reworked. To start from a published revision instead of the current draft, read it with GET …/versions/{revision}/content and PUT it as the draft, or POST …/duplicate it into a new template.

Read GET /{tpl_id}/draft/publish-readiness immediately before publishing. Confirm can_publish: true, review its checks and use its draft_etag:

POST /api/v1/design-templates/{tpl_id}/draft/publish
If-Match: "dd-draft-r4-..."
Idempotency-Key: proposal-publish-001

The response gives an immutable revision, portable tplv_ version ID, page count and hashes. The same template/key replays that exact version, even after the draft ETag changes; another payload with that key conflicts. An outdated ETag returns 412 and an unready draft returns 422. Migration 479 commits the publication receipt in the same SQL transaction as the new version and current pointer. MCP publish_design_template_draft uses the same service with write scope, WsAssetsManage and audit. When receipt retention is enabled, replay history lasts its configured period (30 days by default); retention is disabled by default.

Archive or return a published template to draft

Section titled “Archive or return a published template to draft”

Read GET /api/v1/design-templates/{template_id}/draft first and use its exact draft_etag as the If-Match header. Send a new Idempotency-Key for each operation. POST /api/v1/design-templates/{template_id}/archive hides a draft or published template from future selection. An archived template cannot be edited or returned to draft through this command. POST /api/v1/design-templates/{template_id}/revert-to-draft makes a published template editable again; publish a new immutable revision when the edit is ready. Both return portable template_id, status, draft revision/ETag and whether the retry was replayed. A stale ETag returns 412; reuse of a key for a different operation returns 409. Status change and retry receipt commit in one SQL transaction. Workflows pinned to an earlier published revision continue to use that immutable snapshot.

MCP offers change_design_template_lifecycle (action archive or revert_to_draft) with the same if_match and idempotency_key; it is marked destructive for client review. These commands change template availability, so choose the intended template from its draft readback before calling them.

The internal AI Agent offers design_template.change_lifecycle with template_id, action (archive or revert_to_draft) and exact if_match. It requests a dedicated approval tied to that template, action and draft revision. The Agent rechecks the live state after approval and applies the same Application command; an outdated approval changes nothing. This capability has passed offline build and tests and awaits a live Agent planner gate.

Native DesignDocument drafts contain an optional styles catalog. A style has an id, name, kind (paint, text, object) and bounded appearance properties. Text and object styles may link document paint styles through paintRefs (fill, stroke where supported). Elements carry styleRefs (text, object, fill, stroke) and styleOverrides. The editor stores effective appearance on each element so PDF rendering, thumbnails, published revisions, and older readers use the same pixels. Style definitions never include text content, coordinates, dimensions, path data, image source, crop or page layout. Master elements can reference the same document catalog. New blank documents start with editable Title, Subtitle, and Body text styles. Documents created with explicit initial content, including import and clone flows, preserve that content’s style catalog unchanged.

POST /api/v1/design-templates/{id}/draft/styles/edit accepts operation: create, update, update_from_element, apply, detach, reset_overrides, or remove. Optional request fields are styleId, elementId, slot, name, kind, properties, paintRefs, and replacementStyleId. It requires the exact draft If-Match and an Idempotency-Key, and returns the usual draft edit response with the changed style or element ID in element_id. The outline (get_design_template view outline) now lists style definitions and each element’s style_refs. MCP exposes edit_design_template_style; the Agent capability is design_template.edit_style with the same operations.

Editing a style updates all linked elements in this document except properties with local overrides. Applying a style clears overrides for its managed properties. Direct element edits create local overrides; reset_overrides restores linked values. Removing or detaching a style keeps the element’s current appearance, unless replacementStyleId is supplied. The editor’s Styles panel has separate Text, Paint and Object tabs with previews and usage counts. Each row offers apply, edit and delete actions; creation and editing use a dialog with a live preview. Import and export controls are in the panel header. It exports a madoo.document-styles/v1 JSON file; importing into another document assigns fresh IDs and disambiguates names. Importing does not create a live cross-document link. Workspace-wide style libraries are planned for a later campaign.

Native DesignDocument drafts can contain an optional components catalog. Each definition has an id, name, intrinsic width and height, and a local element tree. Pages and masters reference a definition through a lightweight component_instance element with componentId. The instance owns its page transform (left, top, scale, rotation, opacity, visibility and lock state), while the definition owns the internal geometry and hierarchy. Definitions cannot contain other component instances or repeat regions in this version, so expansion is finite and deterministic.

An instance can carry componentOverrides, keyed first by definition element ID and then by property name. The bounded override surface supports text and image content, image frame data, fill and stroke paints, opacity, shadows, visibility and style references. Coordinates, dimensions, path geometry, child hierarchy and component references remain definition-controlled. Editing a definition updates every linked instance; resetting an instance clears its overrides. Detaching expands the current appearance into an ordinary group. Deleting a definition detaches its instances so the document keeps the same visible content.

The editor exposes the catalog in the Components document panel. The + action opens a dedicated visual component canvas, where a component can be created independently with the compatible drawing, text, image, layer and style tools from the document editor. A separate shortcut creates a component from the current selection, replaces that selection with its first linked instance in the same position, and opens the same visual editor. Existing definitions are edited on that canvas with explicit Save component and Cancel actions; the document canvas and its history are restored when the component editor closes. Catalog thumbnails use the same rendering pipeline as document pages so text, vector paths, lines, images, masks and styles are represented faithfully on a white preview background. Clicking a catalog entry starts placement mode, while dragging it onto the page places the instance at the drop point. A dedicated dialog remains available for the selected instance’s allowed content and appearance overrides. Component definitions and instances participate in undo/redo, save, reopen, revisions, sharing, clone/import, asset and font manifests, thumbnails and PDF rendering. Before placeholder binding and layout, the runtime expands instances into normal element groups; this also makes fields, conditions, image masks and style references inside a component behave like their ordinary element equivalents.

POST /api/v1/design-templates/{id}/draft/components/edit accepts operation: create_from_elements, insert, rename, update_definition, override, reset_overrides, detach, or remove. Optional request fields are componentId, elementIds, instanceId, pageId, name, left, top, targetElementId, and properties. It requires the exact draft If-Match and an Idempotency-Key, and returns the usual draft edit response. The draft outline lists component definitions, definition element IDs, instance counts, and component_id on instance elements. MCP exposes edit_design_template_component; the Agent capability is design_template.edit_component with the same operations.

The catalog is local to one document. Workspace libraries, variants, nested components, component-specific auto layout, cross-document synchronization and separate component import/export are deferred to a later campaign.

Flow layouts: texts that take the lines they need

Section titled “Flow layouts: texts that take the lines they need”

A title that is sometimes one line and sometimes three should not leave a gap below it, nor overlap what follows. A Layout region whose elements fit their content (layout.childSizing: "content") behaves like a web flex column or an auto-sized InDesign frame: each text with a line rule takes exactly the lines its printed value needs, and everything after it in the Layout moves up or down. Nested Layouts are measured first; an element hidden by its rule or not visible leaves no space. With sizing: "hug" the region itself is as large as its content, and anchor decides which edge stays in place: start (top, the default), center, or end — a block anchored at the end grows upwards from its bottom edge, like a caption sitting on the bottom of a photo. Layouts written before this setting existed keep drawn sizes (childSizing absent).

A text’s line rule is its grow: max_lines (1–50), beyond (ellipsis, shrink, or fail, which stops the render and names the value), and height: content makes the text exactly as tall as its lines (at least one); at_least_drawn (the Repeat-row rule) never makes it shorter than drawn. The canvas, the exact preview and the PDF use the same measurement; the canvas measures the text as written (a field shows its {code}), the preview and PDF the sample or runtime values.

On a free text — on the page, in a Group, or in a Layout that keeps drawn sizes — grow has nothing to push: the box keeps its place and height, and the value may take up to max_lines lines and no more than the box holds (at least one). A value that needs more follows beyond inside the box — shrunk, cut with an ellipsis, or fail with DESIGN_TEXT_TOO_LONG (the message gives the lines needed and the lines available); a value that fits prints as drawn, and height has no effect. The exact preview and the PDF apply it; the canvas shows the text as written. Everywhere, shrink keeps the line limit: a smaller size never wraps the value onto more lines than max_lines (or than a free text’s box holds); a value too long even at the minimum size keeps the limit, loses the rest and is reported in the preview’s DESIGN_TEXT_OVERFLOW.

A Repeat inside a flowing Layout takes the height of the rows it prints — its padding plus the rows (measured when they grow) and the gaps between them; an empty list takes only its padding — so a list of line items followed by a total, or key ideas followed by a quote, keeps what follows right after the last row. The drawn height is the most the list may take: rows beyond it follow the Repeat’s overflow rule (fail, clip, fit) and the list keeps its drawn height. A Repeat that continues on new pages must sit directly on the page, not in a Layout.

A rectangle or image directly inside a Layout can be a background layer (layoutBackground: true): it is out of the flow, covers the whole measured region (padding included), is drawn behind the other elements and grows with the region. Its colours, gradient and opacity are those of the element; layout.cornerRadius rounds all the background layers (images are clipped to it). A photo plus a semi-transparent rectangle makes the classic card with an overlay.

POST /api/v1/design-templates/{id}/draft/layouts/arrange accepts operation:

  • create — element_ids (same parent) are wrapped in a new Layout in reading order (same visual row left to right, otherwise top to bottom). Defaults: vertical, fits its content, padding 12, gaps 8, elements fit their content (texts without a rule get up to 10 lines then an ellipsis). The padding is placed around the content, so it stays where it was drawn. Optional name and settings.
  • update — layout_id plus settings and/or order (every element id of the Layout once, background layers excluded; they keep their place).
  • unwrap — layout_id: the elements return to the parent where the Layout shows them (drawn sizes); background layers become ordinary shapes covering the region’s box.

settings fields are all optional: mode (vertical, horizontal, grid, absolute), sizing (hug, fixed), child_sizing (content, drawn), anchor, padding (all sides) or padding_top/right/bottom/left, row_gap, column_gap, columns, main_axis_alignment, cross_axis_alignment, clip, corner_radius (0 removes it). Switching a Layout to child_sizing: "content" gives its texts a content-height rule.

{ "operation": "create", "name": "Cover caption",
"element_ids": ["<title id>", "<subtitle id>", "<rule id>"],
"settings": { "padding": 24, "row_gap": 8, "anchor": "end", "corner_radius": 12 } }

PATCH /draft/elements/{element_id} accepts grow ({ "max_lines": 2, "beyond": "ellipsis", "height": "content" }), clear_grow, and layout_background (true/false). The draft outline reports each element’s parent_id, a region’s layout and a text’s grow, and uses the content type names (layout_region, repeat_region, component_instance, svg_artwork). MCP exposes arrange_design_template_elements and the same fields on update_design_template_element; the Agent capabilities are design_template.arrange_elements and design_template.update_element. All require the exact draft If-Match and an Idempotency-Key.

A number field receives a number (186000, 0.25) and the template decides how it prints — the same idea as a .NET format string with its culture, expressed as options that the editor, REST, MCP and agents all understand and that the editor can preview exactly (they are the options of Intl.NumberFormat):

Option Values Example
locale language and region (it-IT, en-GB, de-CH…) or a data key in braces ({language}) the same template prints 89 € in Italian and €89 in English
style decimal, currency, percent a percent takes a fraction: 0.25 prints 25 %
currency ISO code EUR, USD, GBP
currency_display symbol, code 89 € / 89 EUR (it-IT); placed as the language places it
minimum_fraction_digits, maximum_fraction_digits 0–20 1.234,5 with 0–2 decimals
use_grouping true (default), false 1.234.567 / 1234567
sign_display auto, always, except_zero +3 %
negative minus, parentheses -5 / (5) as in accounting
prefix, suffix up to 24 characters, printed as written da 120 m²

With locale: "{language}" the language is read from the render data key language (for example "it-IT" in one data object and "en-GB" in the other); without it the number prints in English (US).

A yes/no (boolean) field takes true_label / false_label (the words printed, default Yes/No) or boolean_mode: "visibility" (the element shows only when the value is true); a color field takes color_target fill or stroke. Other options on those types are rejected.

Set it with PUT /draft/elements/{element_id}/placeholder (type: "number", format: {...}), MCP configure_design_template_element_placeholder, the Agent capability design_template.configure_placeholder, or the editor (Make placeholder → Number → Number format, with a live example).

Compatibility. A format written before these options (only locale, style, currency and decimals) keeps printing exactly as before, so published templates never change by themselves: currencies as EUR 89, thousands grouped only when the minimum and maximum decimals are equal. Any of the new options — and every format written by the editor, REST, MCP or agents from now on — follows the current rules above.

Any element can be a link: in the PDF, clicking its box opens an address. On a group or a Layout the whole container is the link — a product card, a contact block. The address is https:, http:, mailto: or tel: and may contain fields in braces, filled with the render data:

  • {code} — a field of the document (it must exist as a field of the template);
  • {item.key} — inside a Repeat row, a key of that row’s item. A key read only by a link joins the list’s contract as an optional text key.

A field inside the address is URL-encoded ({item.sku} = NC 01 gives NC%2001). An address that is only a field ({product_url}) takes the value as the whole address, which must itself be https, http, mailto or tel: a value can never turn a link into a script. When a field has no value, or the result is not a valid address, the element is printed without its link and the render reports DESIGN_LINKS_LEFT_OUT. Master pages are static: their links cannot read fields.

{ "link": { "href": "https://shop.example/p/{item.sku}?utm_source=whatsapp",
"description": "Open the product page" } }

description says what the link opens; screen readers read it and some viewers show it. Links exist only in the PDF: the page images (output: image, the page manifest) are not clickable, and a PDF/X-4 print file leaves links out — an annotation over the printed area is not allowed there — with the warning DESIGN_LINKS_OMITTED_FOR_PRINT. For a flyer shared on messaging apps, send the PDF alongside the image, or print the address next to the item.

Set it with PATCH /draft/elements/{element_id} (link, clear_link), MCP update_design_template_element, the Agent capability design_template.update_element, or the editor (Fields → the selected element, or Visibility and link of its elements for elements inside a container; the Layers panel marks linked elements). The draft outline reports each element’s link.

A page read on screen or shared as an image — a visual summary, a social card, a receipt — often has content of variable length. Give the page a height rule and the printed page is only as tall as what it prints: it ends bottom_margin points below its lowest printed element (hidden elements and elements left out by their rule do not count; master elements do), and it is never taller than the page’s height, which stays the most the page can take. Pages without the rule keep their height, as before. Only output pages take the rule: a master page keeps its size.

Put everything that must follow the content in flowing Layouts (a footer too: an element drawn at the bottom of the page keeps the page tall). Background colours, gradients and images cover the printed size.

In the document the page carries "fitHeight": { "bottomMargin": 40 }. Set it with PUT /draft/pages/{page_id} (fit_height_bottom_margin, clear_fit_height), MCP edit_design_template_page (operation update), the Agent capability design_template.update_page, or the editor (Page settings → Height follows the content); the canvas marks where the page ends with the active sample. The draft outline reports fit_height_bottom_margin. The exact preview, the page images and the PDF all use the printed size.

A folder groups page elements in the Layers panel only — like the layer folders of an image editor — so they can be selected, hidden, locked, moved or deleted together while each element stays directly editable. It is not a Group: it has no position, size or effect, its elements keep their page coordinates, and it prints as if it were not there. A hidden folder prints none of its elements; a locked folder locks them in the editor (each element keeps its own visibility and lock, which apply again when the folder is shown or unlocked).

{ "type": "folder", "name": "Header", "visible": true, "locked": false,
"children": [ { "type": "image", "name": "Photo", "left": 0, "top": 0, "...": "..." },
{ "type": "text", "name": "Title", "left": 40, "top": 120, "...": "..." } ] }

Folders sit on pages and master pages and nest in other folders (at most 8 levels); they cannot sit inside a Group, Layout, Repeat or component, and carry no field, rule, link, effect or geometry (all zero, scale 1, opacity 1) — DESIGN_FOLDER_INVALID otherwise. A folder is a contiguous run of the stacking order: its elements are drawn together, bottom to top. Folders are not part of the template contract, and a Repeat inside a folder still counts as placed directly on the page (it can continue on new pages).

The draft outline reports folders with type folder; an element in a folder has the folder as parent_id. Every element tool keeps working on elements inside folders by id. In the editor: Layers → New folder (with the selected elements, if any), drag rows into, out of and between folders, the folder’s eye and lock, double-click to rename, and the folder menu to remove it (keeping its elements in place) or delete it with them.

Workspace members manage printer-supplied ICC profiles centrally in Settings > Print & Color > ICC profiles. The catalog supports multiple immutable CMYK printer profiles, one workspace default, search, and logical archive. Archiving removes a profile from new export choices but does not invalidate published workflows that already pin its GUID and SHA-256. The current default must be replaced before it can be archived.

The DesignDocument export menu keeps Standard PDF as the general-purpose path and exposes PDF/X-4 as an explicit print-ready path. The PDF/X-4 dialog preselects the workspace default, allows another active profile to be chosen, links back to the central catalog, and offers an inline upload action for first use. Profile upload requires an embedding-rights acknowledgement. Profile choice is per export; this bounded version does not persist a separate document-level color-profile preference. The design/template_render workflow node stores the chosen profile and publish pins its content hash for reproducible execution.

After the backend containing DD7 is restarted, run the read-only gate against the published demo:

Terminal window
node docs/demo-workflows/design-template-render/qualify-dd7-read-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-versions-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-portable-pdf-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-portable-pages-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-portable-pages-mcp-dev.mjs

After migration 478 is applied in Development and the API/Auth hosts are updated, the Development-only create gate makes at most one native blank draft and verifies same-key replay, different-payload conflict and draft ETag without provider calls:

Terminal window
node docs/demo-workflows/design-template-render/qualify-dd7-create-dev.mjs

After migration 479 and the API restart, the Development gate adds one text element and configures one placeholder at most, checking both replay/conflict paths and stale ETag rejection with zero provider calls:

Terminal window
node docs/demo-workflows/design-template-render/qualify-dd7-add-text-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-sample-preview-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-publish-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-typed-budget-dev.mjs
node docs/demo-workflows/design-template-render/install-typed-budget-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-repeating-list-dev.mjs
node docs/demo-workflows/design-template-render/install-repeating-line-items-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-image-field-dev.mjs
node docs/demo-workflows/design-template-render/install-image-field-dev.mjs
node docs/demo-workflows/design-template-render/qualify-dd7-lifecycle-dev.mjs

Run the sample/preview gate before publish. The publish gate writes at most one immutable revision and checks same-key replay, stale ETag rejection and the published headline contract. Run the typed-budget gate and workflow installer only after the API loads the typed-placeholder command. The installer builds a one-page example with optional base JSON plus visible headline and number inputs that override those JSON keys, PDF and a separate page image. Its recipe uses only the public tpl_ selector and published revision; no Editor API lookup is needed. The portable installer and its replay gate have passed live. The repeating-list qualifier and installer also passed live, including two rendered rows and a second run without duplicates. The image-box command and workflow also passed live, including one image loaded in exact preview and a replay without duplicates. DD7 lifecycle archive/revert and the closed-catalog pin guard have passed live Development gates after backend restart. The internal AI Agent lifecycle capability has offline coverage and still needs a planner gate; AI Assistant authoring parity and the full five-surface gate remain open.