---
title: "Create a template"
method: POST
path: "/api/templates"
tags: ["Templates"]
---

# Create a template

`POST /api/templates`

## Request

- Base URL: `https://core-api.getaptly.com`
- URL: `https://core-api.getaptly.com/api/templates`
- Auth: one of:
  - API key in header `x-token`
  - API key in header `Authorization`
  - HTTP bearer
- TypeScript: <https://skmtc.dev/getaptly/apis/aptly-api/docs/api/templates/post.ts> (a function with typed inputs; its response is checked against the schema)

Creates a communication template.

The acting user — the delegate token's own user, or the body `userId` when
authenticating with an API key or partner token — must belong to the company and
hold the `setup` or `setup_communication_templates` permission.

Only plain templates can be created here — `htmlBuilder` must be `false` or
omitted.

`attachmentIds` are resolved against the company's uploaded files, stored on the
template as full attachment entries, and tied to it so the detached-upload cleanup
leaves them alone — ids that no longer resolve are dropped. A `pdf` template's
`subject` becomes a file name, so characters that are illegal in one are stripped.

### Merge fields

`subject` and `content` may contain merge placeholders, which are replaced with
the recipient's, board card's or company's own values when the template is sent.

Fetch the available fields from
`GET /api/board/{boardId}/merge-fields` for the board the template is used with.
Every item in that response is `{ label, value }` — `label` is what to show a
human, `value` is what goes inside the braces:

```
{{firstname}}
{{Locations["Rent"]}}
{{customFields.7Lq2.LoyaltyTier}}
```

A placeholder may carry a fallback, used when the field is empty or unknown:

```
{{firstname || fallback: there}}        ->  "there" when the contact has no first name
{{Locations["Rent"] || fallback: TBD}}  ->  "TBD" when the card has no rent
{{firstname || fallback: }}             ->  renders nothing at all
```

Everything after `fallback:` up to the closing braces is the fallback text, and an
empty one (or `_blank`) renders nothing.

Placeholders are **shape**-checked on write — an unclosed `{{`, an empty field
name, or a malformed bracket reference such as `Locations["Rent]` is rejected with
400. Whether the field exists is not checked: an unknown field is normal (boards
differ), and simply renders as its fallback at merge time.

## Request body

- TemplateUpsert
  - `companyId` string: Required with a partner token; resolved from an API key or delegate token.
  - `userId` string: The acting user. Required with an API key or partner token; a delegate token supplies its own.
  - `name` string, required
  - `description` string, required
  - `templateType` 'sms' | 'email' | 'form' | 'eSignature' | 'pdf', required: Template kind. `blockDocument` is not accepted — it is an internal type for document blocks, not a user-facing template.
  - `subject` string, required: Email subject, or the file name for a `pdf` template — in which case filename-illegal characters are stripped. Merge placeholders work here too, and are shape-checked the same way.
  - `content` string, required: Template body. Merge placeholders take the form `{{value}}` or `{{value || fallback: text}}`, where `value` is an item's `value` from `GET /api/board/{boardId}/merge-fields` — for example `{{firstname}}`, `{{Locations["Rent"] || fallback: TBD}}`. They are checked for shape — an unclosed `{{`, an empty field name or a malformed bracket reference is rejected. Whether each field exists is not checked: an unknown one renders as its fallback at merge time.
  - `htmlBuilder` false: Must be `false` or omitted. HTML-builder templates keep their markup in a builder-owned `builderData` format and can only be authored in the app.
  - `builderData` object: Builder document, when one already exists.
  - `folderId` string: Library folder the template belongs to — must be an existing, non-archived folder in the company. Governs who may edit the template.
  - `aptletUuid` string: Board the template is scoped to. Must be an existing, non-archived board in the company.
  - `signature` string
  - `attachmentIds` string[]: Uploaded file ids to attach.

## Response `201`

Template created.

- object
  - `data` Template
    - `_id` string: Template ID.
    - `companyId` string: Company the template belongs to.
    - `name` string: Template display name.
    - `templateType` 'sms' | 'email' | 'form' | 'eSignature' | 'pdf' | 'blockDocument': Template category.
    - `archived` boolean
    - `createdAt` string, date-time
    - `createdBy` string: User ID of the creator.
    - `updatedAt` string, date-time

## Other responses

- `400`: Missing companyId, or an invalid payload.
- `401`: Invalid or missing credential.
- `403`: The acting user lacks the template permission.

---

[API](https://skmtc.dev/getaptly/apis/aptly-api.md) - [All operations](https://skmtc.dev/getaptly/apis/aptly-api/llms.txt) - [OpenAPI document](https://skmtc.dev/getaptly/apis/aptly-api/revisions/75c367e0b8e5?raw)
