---
title: "Create a template at version 1"
method: POST
path: "/v1/templates"
tags: ["Templates"]
---

# Create a template at version 1

`POST /v1/templates`

## Request

- Base URL: `https://api.galleyrender.com`
- URL: `https://api.galleyrender.com/v1/templates`
- Auth: HTTP bearer

## Request body

- TemplateInput
  - `name` string, required
  - `source` string, required: One self-contained HTML document with inline CSS and Liquid expressions. No file includes.
  - `engine` 'chromium' | 'satori'
  - `schema` object: JSON Schema for the `data` payload. Strongly recommended: it is what turns a bad payload into a field-level error.
  - `options` RenderOptions: Merged over the template's own defaults. Options are part of the cache key, so two calls that differ only here are two different renders.
    - `page_size` string: PDF page size: `Letter`, `A4`, `Legal`, or `210mm x 297mm`.
    - `landscape` boolean: PDF only.
    - `margin` union: PDF margins as a CSS length, or per side. Each value is a non-negative number with an optional unit (in, px, cm, mm) — these four and no others, because they are the four Chromium takes. A bare number is pixels: 48, "48", "48px" and "0.5in" are the same margin. A value that is not a length, or a negative one, is refused with 400 invalid_request before the render starts, as are margins larger than the sheet — the one named by page_size, or the largest Chromium knows when there is none, since the page then comes from the template's own CSS. Margins too large for the page a template actually prints on come back as render_failed 422 with cause template.
      - string
      - number
      - object
        - `top` union
          - string
          - number
        - `right` union
          - string
          - number
        - `bottom` union
          - string
          - number
        - `left` union
          - string
          - number
    - `width` integer: Viewport width in px for png/jpg. Default 1200.
    - `height` integer: Viewport height in px for png/jpg. Default 630.
    - `scale` number: Device pixel ratio for png/jpg; a CSS zoom on PDF, where it is capped at 2. Values above the cap are clamped, not rejected, and the cap is 3 on both engines.
    - `full_page` boolean: png/jpg: capture the whole document, not just the viewport. The capture is limited to 16,384 device pixels on either side, measured after `scale`; a larger page is refused with render_failed 422 and cause `template`.
    - `quality` integer: jpg only. Default 90.
    - `background` string: CSS colour painted under the document.
    - `print_background` boolean: PDF: print background colours and images. Default true.
    - `css` string: Extra CSS appended after the template's own styles.
    - `on_blocked_asset` 'fail' | 'skip': What to do when an image, font or other subresource is refused by the egress policy or fails to fetch. `fail` fails the render with `asset_blocked` naming the URL; `skip` drops the asset and renders without it. Both engines behave identically.
  - `expected_pages` integer: How many PDF pages a typical payload renders. This is the pre-flight estimate the free tier and the spend cap are checked against, so a multi-page template must declare it or a render that cannot fit the allowance will be allowed to start. It is not a limit: the render is metered on the pages it actually produced, and an under-estimate is corrected afterwards from that count. Ignored for png/jpg, which are always one unit.
  - `example` unknown
  - `description` string
  - `message` string: Change note for this version, like a commit message.

## Response `201`

Created at version 1.

- Template
  - `object` 'template', required
  - `id` string, required
  - `name` string, required
  - `description` string, nullable
  - `latest_version` integer, required
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `version` integer: Present when a version was resolved.
  - `ref` string
  - `engine` 'chromium' | 'satori'
  - `schema` object, nullable
  - `options` object, nullable
  - `expected_pages` integer: The version's declared PDF page estimate.
  - `example` unknown
  - `source` string
  - `checksum` string: SHA-256 of engine, source, schema and options. Part of the render cache key.
  - `message` string, nullable
  - `version_created_at` string, date-time
  - `origin` 'library' | 'import' | 'own': On `GET /v1/templates`: a starter from the library, a template saved from a document import, or one of the account's own.

## Other responses

- `400`: `invalid_request` — bad name, unparseable Liquid, or an invalid JSON Schema.
- `401`: `authentication_error`.
- `403`: `plan_required` (`details.feature: "templates"`) — the account already holds as many templates of its own as its plan keeps (`details.template_limit`, `details.own_templates`). The starter library never counts. Nothing the account has is affected: publishing a new version is never limited, and deleting a template makes room.
- `409`: `conflict` (`details.code: "template_exists"`) — a live template already holds that name. Publish a new version instead. A soft-deleted template does not hold its name: deleting `invoice` frees it, and creating `invoice` again starts a new template at version 1.

---

[API](https://skmtc.dev/galleyrender/apis/galley-render-api.md) · [All operations](https://skmtc.dev/galleyrender/apis/galley-render-api/llms.txt) · [OpenAPI document](https://skmtc.dev/galleyrender/apis/galley-render-api/revisions/a85d68468cd2?raw)
