Use templates with your data
Use with your agent
Read https://thirds.ai/docs/templates and help me turn my HTML and data into a reusable template with supported fields, a data schema, and versioned renders.
Keep one design and fill it with new names, amounts, or rows each time you render it. Templates use HTML with Jinja fields. The format is the same whether you start in the chat, editor, gallery, or API.
Follow Design and edit a template to make one in the studio. For a working request, see Create your first PDF.
Choose saved or stateless input
| Input | What it does |
|---|---|
html | Renders the HTML you send. |
html with data | Fills template fields in the HTML, then renders it. |
template_id with data | Loads a saved version, checks its schema if present, and fills its fields. |
Use html when you want to send the design with the request without saving a template. A saved version keeps the source and an optional schema. Send the data with each render, since publishing a version does not save its sample data.
PDF and image requests accept all three inputs. See the API reference for request fields and PDF and image options for layout settings.
Write fields, conditions, and loops
Put a field name inside {{ }} to insert its value. Use a condition to show an optional section and a loop to repeat rows. Send every value the template reads, including optional flags and empty lists.
This source makes a short project brief:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Project brief</title>
</head>
<body>
<h1>{{ project }}</h1>
<p>Client: {{ client }}</p>
<p>Due: {{ due_date | date }}</p>
{% if show_tasks %}
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
{% endif %}
</body>
</html>
Use this data object:
{
"project": "Autumn catalogue",
"client": "Example Studio",
"due_date": "2026-10-01",
"show_tasks": true,
"tasks": ["Check product names", "Approve the cover"]
}
The result has the project heading, client, date, and two task rows. Set show_tasks to false to hide the list. Reading a missing value fails with template_missing_data.
Use if, elif, else, and for with their closing blocks. You can also use Jinja comments and raw blocks. Calculate values in your app and pass the results as JSON data. The template does not support +, *, ~, in, or not in, except for in as the separator in a for loop.
Format values
Use these filters to format a field. Only default takes an extra value. Other filters are not supported.
| Filter | Input and result |
| ---------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| default | Use the supplied fallback only when the field is missing. {{ photo | default('image.png') }} keeps empty strings, zero, false, and null. |
| date | A YYYY-MM-DD date, or an RFC 3339 timestamp converted to a UTC date. |
| currency | A number formatted with $, comma groups, and two decimal places. 1234.5 becomes $1,234.50. |
| number | A number with comma groups and up to three decimal places. 1234.5 becomes 1,234.5. |
| safe | A string inserted as HTML without escaping. |
Currency uses dollar formatting. For another currency or locale, format the text in your app and send it as a normal string.
Normal fields display HTML tags as text. For example, <b>Example</b> keeps its tags visible in the output. Use safe only for HTML you have checked and want to insert as markup. Keep plain customer text in normal fields.
Partials and reusable sections
Keep the complete source in one template. Templates cannot load partial files or other templates. They do not support includes, imports, inheritance, macros, assignments, or function calls. If your app manages shared sections, combine them before you save or send the source.
Check data with a schema
Add a JSON Schema to check data before rendering a saved template. thirds.ai supports draft 2020-12 and references within the schema. It does not load remote or file references. It checks formats and rejects a schema that uses an unknown format.
For the example above, a schema can require each field and reject values of the wrong type:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["project", "client", "due_date", "show_tasks", "tasks"],
"properties": {
"project": { "type": "string" },
"client": { "type": "string" },
"due_date": { "type": "string", "format": "date" },
"show_tasks": { "type": "boolean" },
"tasks": { "type": "array", "items": { "type": "string" } }
},
"additionalProperties": false
}
In the studio, open Data, then Advanced, to edit the schema. With the API, send schema beside source when you create a template or version. A schema belongs to that exact version.
If the data fails the schema check, the API returns template_data_invalid with up to 16 field paths and fixed reasons. A nested field uses a JSON Pointer, such as data/items/0/count. Each pointer is cut at 256 UTF-8 bytes. The details never repeat the value you sent. If the template itself fails, use its template error code to find the cause. See retries, downloads, and limits for common errors and safe retries.
An AI draft may have no schema. thirds.ai drops an AI schema if it is invalid or does not match the sample data. Add and test your own schema when you need to enforce which values a template accepts.
Publish and use a version
In the studio, choose Publish to save the current source and schema. With the API, use POST /v1/templates to create version 1 or POST /v1/templates/{template_id}/versions to add the next version. You choose when to publish. A preview or completed AI message does not publish the template.
Send the template ID, data, and an integer version to render a specific saved design. If you omit version, the request selects the newest version when thirds.ai prepares it. Do not send the string "latest". Once thirds.ai accepts the job, that job keeps its selected version even if you publish again.
Saved versions never change. Open Templates, choose a template, and open a version to read its source and schema. Choose Open in editor to start a new version from that source. Publishing adds a version after the current latest version and keeps the old one intact.
To reuse an older design, render its version number. To make it the newest design again, open the older version and publish a new version from it.
You can still read an archived template's versions, but you cannot add versions or start new renders from it. Archiving does not cancel a render that thirds.ai already accepted. Your account can only read and render its own templates.
Keep renders consistent
Use a fixed template version and the same data when you need the same layout. Use stable asset URLs or include permitted assets in the source. A remote image or font can change even when your template version stays the same. Changes to assets or the renderer can produce files with different bytes.
Check the full PDF or image before you connect the template to a live workflow. Read retries, downloads, and limits for job status, polling, and file retention.
Select a brand kit
Send brand_kit_id with a PDF, image, or template preview request. The editor also saves your default kit in the source. An API request can select another kit. Use brand.colours.primary, brand.colours.secondary, and brand.colours.accent for the first three palette colours, and brand.logo for the first logo. Use brand.fonts[0].family and brand.fonts[0].src in a font-face rule. The files stay private and each render captures their bytes.
Do not put brand in your data when you select a kit. The backend supplies it. Your schema describes your own fields. Add enough colours, a logo, or a font to the kit before you use that value. Missing values fail clearly.
A default applies only to an undefined field. For team.photo | default('image.png'), send team: {} if no photo is set. A missing parent object still fails the strict data check.