> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getaptly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a template

> 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.




## OpenAPI

````yaml /openapi.yaml post /api/templates
openapi: 3.0.3
info:
  title: Aptly API
  version: '1.0'
  description: |
    The Aptly API lets external systems work with boards, contacts, inboxes,
    tasks, files, and other Aptly resources.

    Most endpoints accept an API key in the `x-token` header. Some endpoints
    also accept a delegate token or partner bearer token, as shown in each
    operation's security requirements, and explicitly public endpoints require
    no credential. API keys are scoped to a company and may be restricted to
    specific boards and read, insert, or update permissions; requests outside
    those restrictions receive a 403 `FORBIDDEN` response.
servers:
  - url: https://core-api.getaptly.com
    description: Production
security:
  - ApiKeyHeader: []
paths:
  /api/templates:
    post:
      tags:
        - Templates
      summary: Create a template
      description: >
        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.
      operationId: createTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateUpsert'
            examples:
              email:
                summary: Plain email template
                value:
                  companyId: '{{companyId}}'
                  userId: '{{userId}}'
                  name: Move-in welcome
                  description: Sent to new residents on move-in day
                  templateType: email
                  subject: Welcome to your new home
                  content: <p>Welcome!</p>
                  htmlBuilder: false
      responses:
        '201':
          description: Template created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Template'
        '400':
          description: Missing companyId, or an invalid payload.
        '401':
          description: Invalid or missing credential.
        '403':
          description: The acting user lacks the template permission.
      security:
        - ApiKeyHeader: []
        - DelegateToken: []
        - PartnerBearer: []
components:
  schemas:
    TemplateUpsert:
      type: object
      required:
        - name
        - description
        - templateType
        - subject
        - content
      properties:
        companyId:
          type: string
          description: >-
            Required with a partner token; resolved from an API key or delegate
            token.
        userId:
          type: string
          description: >-
            The acting user. Required with an API key or partner token; a
            delegate token supplies its own.
        name:
          type: string
        description:
          type: string
        templateType:
          type: string
          enum:
            - sms
            - email
            - form
            - eSignature
            - pdf
          description: >-
            Template kind. `blockDocument` is not accepted — it is an internal
            type for document blocks, not a user-facing template.
        subject:
          type: string
          description: >-
            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:
          type: string
          description: >-
            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:
          type: boolean
          enum:
            - false
          default: false
          description: >-
            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:
          type: object
          description: Builder document, when one already exists.
        folderId:
          type: string
          description: >-
            Library folder the template belongs to — must be an existing,
            non-archived folder in the company. Governs who may edit the
            template.
        aptletUuid:
          type: string
          description: >-
            Board the template is scoped to. Must be an existing, non-archived
            board in the company.
        signature:
          type: string
        attachmentIds:
          type: array
          items:
            type: string
          description: Uploaded file ids to attach.
    Template:
      type: object
      properties:
        _id:
          type: string
          description: Template ID.
        companyId:
          type: string
          description: Company the template belongs to.
        name:
          type: string
          description: Template display name.
        templateType:
          type: string
          enum:
            - sms
            - email
            - form
            - eSignature
            - pdf
            - blockDocument
          description: Template category.
        archived:
          type: boolean
        createdAt:
          type: string
          format: date-time
        createdBy:
          type: string
          description: User ID of the creator.
        updatedAt:
          type: string
          format: date-time
      additionalProperties:
        description: >-
          Additional properties depend on the templateType (e.g. `subject`,
          `content` for email templates).
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: x-token
    DelegateToken:
      type: apiKey
      in: header
      name: Authorization
      description: 'Delegate token issued by the platform. Format: `DelegateToken <token>`'
    PartnerBearer:
      type: http
      scheme: bearer
      description: 'Partner token. Format: `Authorization: Bearer <token>`'

````