THE POINT: a user wants their research in THEIR format — their masthead, colours,
layout, branding. A saved template is a BLUEPRINT: the author's stylesheet and
page chrome plus one markup pattern per content kind, which the backend applies
automatically to every PDF it renders for that user. A template is a visual
choice, so it is signed off by looking at rendered pages, never by describing
them. This is STEP 1 OF 2: it compiles the submitted HTML into a blueprint under
several treatments and renders each one as PNG previews of the platform's
SPECIMEN DOCUMENT — one of every content kind (title, lead, sections, bullets,
KPI cards, tables, charts, figure, source line, footnote). The user signs off on
how the design treats each kind of content, not on sample text. NOTHING IS
STORED — the draft lives in a 24-hour cache; `save_user_template` is step 2.
THE FLOW:
1. Author the HTML with the user (or take the page they hand you).
2. Call this, then poll `get_task_status(task_id)` until SUCCESS. Show the user
EVERY variant's `preview_urls` (render the images
inline where the client can; otherwise give the links) with its `name` and
`rationale`, side by side.
3. The user picks one -> `save_user_template(draft_id, variant_id)`. Never save
a treatment the user has not seen and approved. Later changes go through
`update_user_template`.
`template_type`:
- "add_on" — the previews are rendered at the 360pt column width the block
will occupy inside a report — anything wider is clipped, not scaled — and
the treatments restyle it to sit inside the host document (its own page
background, masthead and disclaimer are stripped).
- "bespoke" — the template drives the whole document: page chrome, stylesheet
and the pattern for every content kind, previewed as full pages.
TEMPLATE RULES (violations come back as a 422 with `detail.errors`):
- Real CSS engine: grid, flexbox, gradients, absolute positioning all work.
Add `break-inside: avoid` to a panel that must not split across pages.
- Only `data:` URIs for images. External images, stylesheets and web fonts are
BLOCKED; <script>, <link> and similar are stripped.
- HTML only, max 200 KB; must parse as HTML.
Asynchronous: returns {"task_id", "status": "PENDING", "next_step"}. Poll
`get_task_status(task_id)`; the finished result is {"draft_id", "variants":
[{"variant_id", "name", "rationale", "preview_urls": [...], "preview_pages"}],
"expires_in_seconds", "next_step", "warnings"} (or {"error", "warnings"} when the
draft failed). `preview_urls` are time-limited presigned PNG links, one per page.
Read `warnings`: a treatment that "changed too much" was dropped, and an empty
variant list means no usable adaptation survived — revise the template rather
than retrying the same one.
Runs a model pass plus a render per treatment, so expect minutes. Requires auth
(your MCP client attaches the OAuth bearer automatically). Rate-limited 30/min.