Saved templates

Create and manage editable templates, start one from an AI chat message, preview changes, and publish fixed versions for repeat renders.

Use with your agent

Give this prompt to your AI agent. The agent does the task for you.

Read https://thirds.ai/docs/api-reference/templates and help me choose the right thirds.ai endpoint and write a request. Check the fields and credentials in this reference.

See all API resources or read the PDF generation API guide.

Endpoints

createTemplate

POST /v1/templates

Create a saved template

Create a template. Without message, source is required and the answer is the new template. With message, the request also starts the AI template chat of the template with its first message, and Idempotency-Key is required. Without source, version 1 is a blank design: one white A4 page for a PDF, or one white canvas of the requested image size (default 1080 by 1080). A replay with the same key returns the same template and message and creates nothing. If the message is refused, for example with 402 insufficient_credits, the new template is removed. A words or HTML message costs 25 credits. A source image costs 50 credits. The first message of a new blank template costs 50 credits. A source message makes no model call and costs 0 credits. A failed message keeps the previous draft and releases its reservation. All available credits, including free monthly credits, can pay for AI work. An account without enough available credits answers 402 insufficient_credits and holds nothing. Read message.progress for elapsed time, the current stages, and recent successful timing estimates. Estimates use the last 48 hours for the same model route and task when enough samples exist; completion time varies.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
Idempotency-KeyheaderNoRequired with message. {"type":"string","minLength":1,"maxLength":255,"pattern":"^[ -~]+$"}

Request body: application/json, TemplateWriteRequest.

StatusMeaningBody
200An earlier matching request with a message was replayed.application/json: TemplateDetail
201The created template, including version 1 source and schema.application/json: TemplateDetail
202The template was created and its first message was accepted. The answer includes chat and message.application/json: TemplateDetail
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
402The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
409The one error envelope every backend response uses.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
429The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
503The one error envelope every backend response uses.application/json: ErrorEnvelope

listTemplates

GET /v1/templates

List saved templates

List the account's active and archived templates newest first. Source and schema are omitted. Pass next_cursor to continue with a stable keyset page.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
statusqueryNo{"type":"string","enum":["active","archived","all"],"default":"all"}
qqueryNoSearch names on the server. {"type":"string","maxLength":120,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]*$"}
tagqueryNoMatch one exact tag on the server. {"type":"string","minLength":1,"maxLength":40,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"}
limitqueryNo{"type":"integer","default":20}
cursorqueryNo{"type":"string","maxLength":128}
StatusMeaningBody
200One page of saved-template summaries.application/json: TemplatePage
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

getTemplateSchemas

GET /v1/templates/schemas

Get template data schemas

Read the data schemas of up to 50 saved templates of the account. Use them to build forms, check data without code, or ask an AI model for data in the right shape. Choose templates with repeated template_id values, or with one tag. A tag selects active templates newest first. Each item names the exact version it uses. With form=saved, each schema is exactly as published, and a template without a schema has a null schema. With form=strict, each schema is converted to the subset that AI structured output accepts: every object has additionalProperties false and requires all its properties, an optional property also accepts null, oneOf becomes anyOf, and only type, properties, required, additionalProperties, items, enum, const, anyOf, description, $defs, and $ref stay. Length, item count, number range, pattern, format, and default rules become sentences in description. Remove null values of optional properties before you render with that data. With combine=one_of, combined is one schema with an object root. Its template property is any one of the templates, and each branch names template_id, version, and data. The $defs keys of each template get a prefix such as t0_. A template without a schema fails a strict or combined request with 422 template_schema_missing. A strict form with allOf, not, if, then, else, patternProperties, dependentSchemas, prefixItems, a map of additional properties, a non-object root, or a reference other than #/$defs/name fails with 422 template_schema_unsupported. A combined saved form fails the same way for a reference other than #/$defs/name. Unknown parameters and other combinations are 400 invalid_request.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idqueryNoOne saved template. Repeat it for up to 50 templates. The items keep this order. Use template_id or tag, not both. {"type":"array","minItems":1,"maxItems":50,"uniqueItems":true,"items":{"$ref":"#/components/schemas/TemplateId"}}. See TemplateId
tagqueryNoSelect up to 50 active templates with this exact tag, newest first. {"type":"string","minLength":1,"maxLength":40,"pattern":"^[^\\u0000-\\u001f\\u007f-\\u009f]+$"}
versionqueryNoAn exact version. Allowed only with one template_id. The default is the latest version. {"type":"integer","minimum":1}
formqueryNo{"type":"string","enum":["saved","strict"],"default":"saved"}
combinequeryNo{"type":"string","enum":["none","one_of"],"default":"none"}
StatusMeaningBody
200The schemas in the requested form.application/json: TemplateSchemaSet
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
422The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

previewTemplate

POST /v1/templates/preview

Preview template data as HTML

Expand source and a JSON data object through the bounded template engine. Optional schema validation uses the saved-template rules. This does not save a template, create a render, or spend credits. The response is untrusted customer HTML for an isolated preview. Browser sessions need the current CSRF token.

Authentication: bearer API key. See authentication.

Request body: application/json, TemplatePreviewRequest.

StatusMeaningBody
200The expanded HTML. This response uses Cache-Control: private, no-store.application/json: TemplatePreview
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
415The one error envelope every backend response uses.application/json: ErrorEnvelope
429The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
503The one error envelope every backend response uses.application/json: ErrorEnvelope

adaptTemplate

POST /v1/templates/adapt

Adapt one HTML source to many canvas sizes

Deterministically adapt one canonical HTML source to up to 20 target canvas sizes. This is free, needs a session or key, and uses the preview rate limit. It does not save a template, create a render, or spend credits. The response gives the adapted source per size, or a safe error code for that size.

Authentication: bearer API key. See authentication.

Request body: application/json, TemplateAdaptRequest.

StatusMeaningBody
200One entry per requested size, each with its adapted source or a safe error code.application/json: TemplateAdaptResponse
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
415The one error envelope every backend response uses.application/json: ErrorEnvelope
429The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope
504The one error envelope every backend response uses.application/json: ErrorEnvelope

getTemplate

GET /v1/templates/{template_id}

Get a saved template

Without include, chat is a small summary with no messages, and message is the live message of the chat, if any. Poll this read while a message runs. With include=chat, chat also has its newest 100 messages.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId
includequeryNochat adds the newest messages of the chat. Any other value is refused with 400. {"type":"string","enum":["chat"]}
StatusMeaningBody
200The template, its latest immutable version, and its chat.application/json: TemplateDetail
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

renameTemplate

PATCH /v1/templates/{template_id}

Rename a saved template

Set or clear the name of one owned template. The name is trimmed and can have 1 to 120 characters. A null or blank name clears it.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId

Request body: application/json, TemplateRename.

StatusMeaningBody
200The template with its new name.application/json: TemplateSummary
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

archiveTemplate

DELETE /v1/templates/{template_id}

Archive a saved template

Archive one owned template. The operation is idempotent. Archived templates and versions remain readable, but they cannot receive another version or create another render.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId
StatusMeaningBody
200The template is archived.application/json: TemplateSummary
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

createTemplateVersion

POST /v1/templates/{template_id}/versions

Create a template version

Edit a template by publishing one complete new immutable source and optional schema. Existing versions never change. Archived templates cannot receive another version.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId

Request body: application/json, TemplateVersionWriteRequest.

StatusMeaningBody
201The new immutable version, including source and schema.application/json: TemplateVersionDetail
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
413The one error envelope every backend response uses.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

listTemplateVersions

GET /v1/templates/{template_id}/versions

List template versions

List immutable versions newest first. Source and schema are omitted. Archived template versions remain readable.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId
limitqueryNo{"type":"integer","default":20}
cursorqueryNo{"type":"string","maxLength":32}
StatusMeaningBody
200One page of immutable version summaries.application/json: TemplateVersionPage
400The one error envelope every backend response uses.application/json: ErrorEnvelope
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

getTemplateVersion

GET /v1/templates/{template_id}/versions/{version}

Get a template version

Read one saved, immutable version of a template.

Authentication: bearer API key. See authentication.

ParameterLocationRequiredRule
template_idpathYesTemplateId
versionpathYes{"type":"integer","minimum":1}
StatusMeaningBody
200The exact immutable template version.application/json: TemplateVersionDetail
401The one error envelope every backend response uses.application/json: ErrorEnvelope
403The authenticated account is suspended. The code is account_suspended.application/json: ErrorEnvelope
404The one error envelope every backend response uses.application/json: ErrorEnvelope
405The method is not allowed on this route.application/json: ErrorEnvelope
431The request has more than 64 headers or more than 32 KiB of header names and values.application/json: ErrorEnvelope

Schemas

TemplateFirstMessage

FieldRequiredDescription and type
inputYesTemplateMessageInput
outputNoTemplateOutputIntent
brand_kit_idYesBrandKitId

Schema rules: {"type":"object","additionalProperties":false}.

TemplateTags

Trimmed tags. Each tag is nonblank. Case is preserved.

Schema rules: {"type":"array","maxItems":20,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":40}}.

TemplateName

The customer's own name for a template. It is trimmed and can have 1 to 120 characters. Null or blank means no name.

Schema rules: {"type":["string","null"],"maxLength":120}.

TemplateWriteRequest

FieldRequiredDescription and type
tagsNoTemplateTags
nameNoTemplateName
input_modeNoHow this new template starts. It records a blank-page start or imported HTML. New versions do not accept this field. {"type":"string","enum":["blank","html"],"default":"html"}
sourceNo{"type":"string","maxLength":1048576}
schemaNoJSON Schema for the data. Put sample values in examples[0], so the studio can show them. Mark a picture field with "x-thirds-kind": "image" and a colour field with "x-thirds-kind": "colour". {"oneOf":[{"type":"object"},{"type":"boolean"},{"type":"null"}]}
sizesNoSizeVariantList
pageNoTemplatePdfPage
messageNoThe first message of the template chat. Without source, version 1 is a blank design. TemplateFirstMessage

Schema rules: {"type":"object","anyOf":[{"required":["source"]},{"required":["message"]}],"additionalProperties":false}.

TemplateVersionWriteRequest

FieldRequiredDescription and type
sourceYes{"type":"string","maxLength":1048576}
schemaNoJSON Schema for the data. Put sample values in examples[0], so the studio can show them. Mark a picture field with "x-thirds-kind": "image" and a colour field with "x-thirds-kind": "colour". {"oneOf":[{"type":"object"},{"type":"boolean"},{"type":"null"}]}
sizesNoSizeVariantList
pageNoThe printed PDF page of this version. Omit it to keep the page of the latest version. TemplatePdfPage

Schema rules: {"type":"object","additionalProperties":false}.

TemplatePreviewRequest

FieldRequiredDescription and type
brand_kit_idNoSelect an owned active brand kit. This uses template evaluation with a 1 MiB source limit and a 3 MiB data limit, even when data is omitted. Overrides the source thirds-brand-kit meta default. Supplies reserved brand.name, brand.palette, brand.colours.primary/secondary/accent (the kit colour that fits each role of this design, so the design keeps its own pattern of dark, pale, and bright colours), brand.colours.dark/light (a dark and a light surface in the kit's hue, each readable on the other), brand.ink.primary/secondary/accent/dark/light (#ffffff, #111111, or #000000, the text colour on each role colour, at least 4.5:1), brand.logo, brand.logos, brand.logo_ground (the plate colour on which the first logo shows well), brand.logo_tile.primary/secondary/accent/dark/light (transparent when the logo shows well on that role colour, or else the plate colour), brand.logo_aspect (the logo width divided by its height), brand.fonts entries with family and src, and brand.heading_font and brand.body_font (the family of the first and second font). data.brand is refused when a kit is selected. A kit with no colours supplies no colour roles. Only present logos and fonts are supplied. With no kit, brand is an empty value that is false in a test, so brand.colours.primary | default('#000000') gives the default. Assets are captured before enqueue. Schemas validate customer data before brand is added. {"type":["string","null"],"pattern":"^kit_[a-f0-9]{32}$"}
sourceYes{"type":"string","maxLength":1048576}
dataYesTemplate values, bounded to 3,145,728 encoded JSON bytes with the template depth and collection limits. {"type":"object"}
schemaNo{"oneOf":[{"type":"object"},{"type":"boolean"},{"type":"null"}]}

Schema rules: {"type":"object","additionalProperties":false}.

TemplatePreview

FieldRequiredDescription and type
htmlYes{"type":"string","maxLength":5242880}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateRename

Omitted fields keep their current values. Set name to null to clear it, tags to an empty array to clear them, or archived_at to null to restore the template.

FieldRequiredDescription and type
archived_atNoRestore the item. Omit this field to keep its archive state. {"type":"null"}
tagsNoTemplateTags
nameNoTemplateName

Schema rules: {"type":"object","additionalProperties":false}.

TemplateSummary

FieldRequiredDescription and type
tagsYesTemplateTags
idYesTemplateId
nameYesTemplateName
created_atYes{"type":"string","format":"date-time"}
archived_atYes{"type":["string","null"],"format":"date-time"}
latest_versionYes{"type":"integer","minimum":1}
brand_kit_idYesThe live brand kit that the template shows: the kit of its newest file or newest version. Null when it has none. {"oneOf":[{"$ref":"#/components/schemas/BrandKitId"},{"type":"null"}]}. See BrandKitId

Schema rules: {"type":"object","additionalProperties":false}.

TemplateDetail

FieldRequiredDescription and type
archive_historyYes{"type":"array","items":{"$ref":"#/components/schemas/ArchivePeriod"}}. See ArchivePeriod
tagsYesTemplateTags
idYesTemplateId
nameYesTemplateName
created_atYes{"type":"string","format":"date-time"}
archived_atYes{"type":["string","null"],"format":"date-time"}
latest_versionYes{"type":"integer","minimum":1}
brand_kit_idYesThe live brand kit that the template shows: the kit of its newest file or newest version. Null when it has none. {"oneOf":[{"$ref":"#/components/schemas/BrandKitId"},{"type":"null"}]}. See BrandKitId
sourceYes{"type":"string","maxLength":1048576}
schemaYes{}
sizesYesSizeVariantList
pageYes{"oneOf":[{"$ref":"#/components/schemas/TemplatePdfPage"},{"type":"null"}]}. See TemplatePdfPage
chatYesThe chat of the template. Null before its first message and while the template is archived. {"oneOf":[{"$ref":"#/components/schemas/TemplateChat"},{"type":"null"}]}. See TemplateChat
messageNoIn the answer of a create request with a message, the first message. In a template read without include, the live message of the chat, if any. TemplateMessage

Schema rules: {"type":"object","additionalProperties":false}.

TemplatePage

FieldRequiredDescription and type
tag_optionsYesThe first 100 distinct account tags, sorted. Use the tag filter for any other tag. {"type":"array","maxItems":100,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":40}}
itemsYes{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/TemplateSummary"}}. See TemplateSummary
next_cursorYes{"type":["string","null"],"maxLength":128}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateVersionSummary

FieldRequiredDescription and type
versionYes{"type":"integer","minimum":1}
created_atYes{"type":"string","format":"date-time"}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateVersionPage

FieldRequiredDescription and type
itemsYes{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/TemplateVersionSummary"}}. See TemplateVersionSummary
next_cursorYes{"type":["string","null"],"maxLength":32}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateSchemaItem

FieldRequiredDescription and type
template_idYesTemplateId
nameYes{"type":["string","null"],"maxLength":120}
versionYesThe exact version that the schema comes from. {"type":"integer","minimum":1}
schemaYesThe schema in the requested form. Null only with form=saved and combine=none when the version has no schema. {"type":["object","boolean","null"]}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateSchemaSet

FieldRequiredDescription and type
itemsYes{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/TemplateSchemaItem"}}. See TemplateSchemaItem
combinedYesOne schema for all items with combine=one_of. Null with combine=none, or when a tag selects no template. {"type":["object","null"]}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateAdaptRequest

Adapt one HTML source to many target canvas sizes with the deterministic layout algorithm. This is free and does not save anything.

FieldRequiredDescription and type
sourceYes{"type":"string","maxLength":1048576}
sizesYes{"type":"array","maxItems":20,"items":{"type":"object","properties":{"width":{"type":"integer","minimum":320,"maximum":7680},"height":{"type":"integer","minimum":200,"maximum":4320}},"required":["width","height"],"additionalProperties":false}}

Schema rules: {"type":"object","additionalProperties":false}.

TemplateAdaptResponse

FieldRequiredDescription and type
sizesYes{"type":"array","items":{"type":"object","properties":{"width":{"type":"integer"},"height":{"type":"integer"},"source":{"type":"string","maxLength":1048576},"error":{"type":"object","properties":{"code":{"type":"string","enum":["resize_source_unsupported","resize_no_layout"]}},"required":["code"],"additionalProperties":false}},"required":["width","height"],"additionalProperties":false}}

Schema rules: {"type":"object","additionalProperties":false}.

Shared errors, headers, and authentication schemes are in shared types and security.

Ready to make your first file?

Start in the playground with a gallery template, or get an API key and send your first request from your own terminal.