Skip to main content

AI Template Generation

Tag: AI · Version: v1 · Stability: 🟠 Experimental (v0)

Generate an editable template draft from a reference image (or PDF) and/or a text description. The model returns a structured document of bands; the platform loads it into the designer as a fresh draft you refine and save — so AI is just another template author, never a new render path.

Experimental — v0

These endpoints are feature-flagged (Ai:Enabled) and their behaviour and response shape may change without a major-version bump. Prototype with it; don't build production dependencies on its output yet. Generations are metered under the ai source.

Free-tier quota

The free tier allows 25 AI generations per month per workspace. Exceeding the quota returns 402 Payment Required. The count resets on the 1st of each calendar month.

Generate a template (streamed)​

POST /api/ai/generate-band-template · JWT · multipart/form-data

This is the current path. The server drives two model passes and streams progress as Server-Sent Events: pass 1 authors the document's skeleton (its bands, data schema, paper, direction and fonts), then pass 2 fills one band at a time. Your client only reads the stream and loads the result.

Form fieldTypeNotes
imagefileOptional reference. An image (max 8 MB) or a PDF (max 25 MB) — for a PDF, page 1 is rasterised server-side and used as the reference.
promptstringOptional description, e.g. "like this, but a blue accent + add a discount row".
html / cssstringOptional HTML (and separate CSS) to convert into a template.
paperJSON stringOptional paper, in the template's pageSize shape, e.g. {"id":"letter"}. Omit for automatic.

At least one of image, html or prompt is required (otherwise 400). The image carries the layout in one shot; the prompt refines it — the combination is the most powerful input.

curl -N -X POST https://api.paperwright.dev/api/ai/generate-band-template \
-H "Authorization: Bearer <jwt>" \
-F "prompt=A two-page invoice with a logo top-left and a totals table" \
-F 'paper={"id":"letter"}' \
-F "image=@reference.png"

Anything that can fail with a real status code — bad input, an unreadable PDF, an exceeded quota (402) — does so before the stream opens. After that the status is already 200 and a problem arrives as an error event.

Stream events​

The SSE event name is the stage:

EventWhenPayload fields
skeletonPass 1 finished.bandsTotal
bandOne band was filled.bandsTotal, bandsCompleted, bandId, failed
doneEverything assembled.template, and an optional message
errorThe run failed.message
event: skeleton
data: {"stage":"skeleton","bandsTotal":3}

event: band
data: {"stage":"band","bandsTotal":3,"bandsCompleted":1,"bandId":"b1","failed":false}

event: done
data: {"stage":"done","bandsTotal":3,"bandsCompleted":3,"template":{ … }}

done.template carries name, bands, schema, direction, bodyFont, headingFont and pageSize.

  • An image element may carry src: a data: URL cut from your reference (a logo or photo the model located in it). Each picture is at most about 150 KB and a page at most about 600 KB.

  • When the request had a reference, done.checkToken is a one-time token (15 minutes) for a free self-check: send it as the checkToken field of revise-document with the same reference and the step compares the draft with the reference (any instruction is ignored) and is not metered.

  • Bands arrive flat. Each band has an id and an optional parentId (a nested detail band in a master-detail layout); rebuild the tree on your side.

  • A band can fail alone. A band that fails twice comes back empty with failed: true rather than failing the document.

  • One document, one credit. Both passes together are metered as a single generation. A document may have at most 20 bands.

How the page is chosen​

The document's paper is settled before any band is filled, first match wins:

  1. the paper form field;
  2. the page size of a reference PDF (matched to a preset, else a custom size);
  3. the model's pick from your request (pageSize in the result, any of the 22 presets).

An invalid value falls back to A4. All coordinates in the result are final for the chosen page, so load it as is rather than re-fitting it.

Direction and fonts​

Pass 1 picks the document direction (ltr or rtl) from the language the document is written in, and lays out right-to-left documents from the right. It also picks a bodyFont and a headingFont from the designer's 18 fonts, and text and tables come back carrying them.

Refine one section​

POST /api/ai/refine-band · JWT · application/json

Change one band without regenerating the document. Send the live document (so the model sees any hand edits), the band's bandId, and the change in your own words:

Request
{
"document": { "name": "Invoice", "bands": [ … ], "schema": [ … ], "direction": "ltr" },
"bandId": "b2",
"instruction": "Add a discount row under the subtotal"
}
Response
{
"bandId": "b2",
"elements": [ … ],
"newFields": [ … ]
}

Replace that band's elements with elements. newFields lists any schema field the change referenced that the document did not already have — add them to the schema, or the new token prints its own braces. A refine is one plain JSON call (no stream), is not metered as a generation, and is still subject to the daily limit and your plan's entitlement.

Revise a document (streamed)​

POST /api/ai/revise-document · JWT · multipart/form-data

One step over the whole document: by an instruction, by visual comparison with a reference, or both. The server renders the current draft, gives the model the render and the reference side by side, and streams the same events as generation.

Form fieldTypeNotes
documentJSON stringRequired. The live document in the AI wire shape (as for refine), with element ids, so changed elements keep their identity. Max 1 MB.
contentJSON stringRequired. The serialized template, used to render the current draft. Max 25 MB.
sampleDataJSON stringOptional data to render the draft with. Max 1 MB.
instructionstringOptional, max 2000 characters.
imagefileOptional reference, as for generation (image, or a PDF whose page 1 is used).

At least one of instruction or image is required. Paper and direction stay as the document has them.

  • Pass 1 decides which bands change (changed + notes per band); pass 2 fills only those. Unchanged bands come back exactly as sent.
  • A band whose fill fails keeps its current elements and is reported as failed; if every fill fails the stream ends with error.
  • done carries template (same shape as generation, element ids echoed) and unchanged: true when there was nothing to change.
  • Metering: one generation credit per step that changes something. Nothing to change (including fills that come back exactly as sent), a step that fails, and a step with a valid checkToken are not metered. The quota (402) and entitlement are checked before the stream opens; the endpoint is rate-limited like rendering.

Generate a template (legacy, single pass)​

POST /api/ai/generate-template · JWT

The original single-pass endpoint. It takes the same optional image and prompt and returns a suggested name and a flat element list. It cannot produce page headers, several repeating bands or nested master-detail layouts, which is why new work uses the streamed endpoint above.

curl -X POST https://api.paperwright.dev/api/ai/generate-template \
-H "Authorization: Bearer <jwt>" \
-F "prompt=A modern invoice with a logo top-left and a totals table" \
-F "image=@reference.png"

Honest expectations​

Image → layout is a draft, not a pixel-perfect clone. The model nails structure and placement; coordinates, fonts, and colours come out approximate. Treat it as "80% of the layout in seconds — finish in the designer."

Data handling​

The image (or PDF page) and/or prompt are sent to the model provider (Anthropic) for generation. Fine for typical use; a no-retention / on-prem path is planned for high-compliance tiers.


  • Templates — where the generated draft is saved.
  • Rendering — AI-authored templates render through the same hardened pipeline.