## Register a template

**post** `/v1/templates`

Registers a template and submits it for review, synchronously.

The call returns once the channel has accepted the submission, so a name
or a body it refuses is a `422` you read immediately rather than a silence
you discover later. What comes back is almost always `status: "pending"`.
Review time varies: templates have been approved in minutes and, in other
cases, over several days, so poll `GET /v1/templates` rather than assuming
a wait. **Nothing is sendable until it is `approved`** — watch for it on
`GET /v1/templates`. After approval the
`send_schema` is published on the registry's next read-back, up to about
fifteen minutes later; until then a send is refused with
`template_not_sendable` pointing at the template. The `category` on the
approved template is the one review assigned and can differ from the one
you submitted; read it back, since it determines pricing and whether
marketing delivery rules apply.

Status is never ours to assert. An approved template can be paused later
for quality and approved again afterwards, and this API reports what the
channel currently says rather than what it said at submission.

A refusal decided before any submission attempt records nothing, and its
message says so: the name remains reusable. Once a submission is attempted,
its evidence remains visible as `status: "draft"` with the derived
parameter schema even if the attempt is refused or its answer is lost. A
definitive refusal leaves the name reusable after correction; an unknown
outcome permanently fences the name and must not be retried. This ordering prevents
an accepted template from becoming invisible here. A name that is already
registered beyond draft answers `409` — submitted names are permanent.
An account with no messaging channel answers retryable HTTP 503
`no_channel_account` with a 3xxx `error.code`, not a validation
error. It carries no `Retry-After` because setup has no honest completion
time to publish.
An invalid channel credential likewise answers HTTP 503
`template_channel_unavailable` with a 3xxx `error.code`, not 422
`template_refused`: rotating that credential is this service's repair,
not a change to the template. It carries no `Retry-After` because rotation
is an operator action rather than a clock boundary.

Media-header preparation is bounded and resumable. While the same request
is actively preparing, HTTP 503 `template_submission_in_progress` (a
3xxx `error.code`, 3021) asks you to retry the unchanged body. Exhausted
preparation answers HTTP 503 `template_asset_unavailable` (a 3xxx
`error.code`, 3011). Once the template-create call may have reached the
channel but its answer is unknown, HTTP 409
`template_outcome_indeterminate` (a 2xxx `error.code`, 2039) is
permanent: do not resubmit that name; registry reconciliation must
establish the outcome. HTTP 201 is returned only after the channel
accepted the template submission.

### Body Parameters

- `category: string`

  `utility`, `marketing` or `authentication`.

  The channel may RECATEGORISE on review — copy that reads as marketing is
  treated as marketing whatever was requested — and the assigned category is
  what the send is priced at. Read `category` back off the response and off
  `GET /v1/templates`.

  An `authentication` template renders a fixed one-time-code layout and
  cannot carry a `carousel`; the pair is refused with a fault at `/carousel`
  before anything is recorded.

- `language: string`

  The locale this variant is written in, such as `en_US`. One template name
  may have several language variants, each registered separately. The
  `x-template-languages` list documents upstream-supported values as of its
  verification date; this service accepts any well-formed code so a newly
  supported locale is not refused here.

- `name: string`

  Lowercase letters, digits and underscores. Permanent once submitted: a name cannot be reused for different copy.

- `authentication: optional object { otp_type, add_security_recommendation, autofill_text, 6 more }`

  Selects the fixed-copy authentication specialization. Valid only when `category` is `authentication`; omit to retain ordinary body authoring.

  - `otp_type: "copy_code" or "one_tap" or "zero_tap"`

    `copy_code` works on every client. `one_tap` and `zero_tap` are Android app integrations and require both app identity fields.

    - `"copy_code"`

    - `"one_tap"`

    - `"zero_tap"`

  - `add_security_recommendation: optional boolean`

    Include the channel's localized security recommendation.

  - `autofill_text: optional string`

    Localized Android one-tap button label, maximum 25 characters.

  - `code_expiration_minutes: optional number`

    Show the localized code-expiry footer. Whole minutes from 1 through 90.

  - `copy_code_text: optional string`

    Localized fallback copy-button label, maximum 25 characters.

  - `message_send_ttl_seconds: optional number`

    Stop attempting delivery after this many seconds. Authentication templates accept 30 through 900.

  - `package_name: optional string`

    Android application package name. Required for `one_tap` and `zero_tap`; omit for `copy_code`.

  - `signature_hash: optional string`

    Android app signing-key hash. Required for `one_tap` and `zero_tap`; omit for `copy_code`.

  - `zero_tap_terms_accepted: optional boolean`

    Must be true for `zero_tap`; omit for the other subtypes.

- `body: optional object { text, examples }`

  The ordinary authoring body. Required unless `authentication` is supplied; specialized authentication templates omit it because their localized body is fixed.

  - `text: string`

    The message copy. Use `{{name}}` for each variable — lowercase letters,
    digits and underscores, the same names a `template` part supplies values
    for. Numbered placeholders such as `{{1}}` are refused: this API addresses
    template variables by name. At most 32 distinct placeholders may appear;
    a larger set is refused at this field.

  - `examples: optional unknown`

    One example value per variable, keyed by variable name. **Required for
    every variable the copy uses** — the channel refuses a template whose
    variables carry no examples, and reviewers read the examples to decide.
    Omit it entirely when the copy has no variables.

    Each value is 1–1024 characters, may not be whitespace-only, and may not
    contain a line break, a tab, or four consecutive spaces, because a
    parameter value carries the same published rules at send time.

- `buttons: optional array of object { text, example, type, url }`

  Buttons for an ordinary template: `url` buttons (fixed, or ending in the send-time variable `{{1}}`) and `quick_reply` buttons. Up to 10 in total, at most two `url`, and the quick replies adjacent to one another. Labels and fixed destinations are baked into the reviewed template; a dynamic destination and every quick reply become slots the approved template's `send_schema` requires on `buttons[]`.

  - `text: string`

    Visible label, maximum 25 characters.

  - `example: optional string`

    Required exactly when `url` ends in `{{1}}`: a sample VALUE for that variable, reviewed with the template. Refused on a fixed destination and on a `quick_reply` button.

  - `type: optional "url" or "quick_reply"`

    `url` (the default when omitted) or `quick_reply`.

    - `"url"`

    - `"quick_reply"`

  - `url: optional string`

    For a `url` button: the HTTPS destination, at most 2000 characters. It may end in exactly `{{1}}` — `https://example.com/jobs/{{1}}` — and nothing else in it may vary; the value is appended at send, verbatim, so percent-encode it yourself. Refused on a `quick_reply` button.

- `carousel: optional object { cards }`

  Optional media-card carousel definition. All 2–10 cards must use the same media kind, body presence, and ordered button-type sequence. A carousel template is the `body` plus its cards: it cannot be combined with `footer`, `media_header` or `buttons`. Valid only when `category` is `marketing` or `utility`: an authentication template renders a fixed one-time-code layout with no card slot, so the pair is refused with a fault at `/carousel` before anything is recorded rather than created without its cards.

  - `cards: array of object { media, body, buttons }`

    - `media: object { asset_handle, kind }`

      - `asset_handle: string`

        The asset handle returned by the channel's template-authoring upload flow. This is neither this API's upload_ref nor an inbound media_id.

      - `kind: "image" or "video"`

        - `"image"`

        - `"video"`

    - `body: optional object { text, examples }`

      The body copy of a template, with `{{variable}}` placeholders for the parts
      that change per send.

      - `text: string`

        The message copy. Use `{{name}}` for each variable — lowercase letters,
        digits and underscores, the same names a `template` part supplies values
        for. Numbered placeholders such as `{{1}}` are refused: this API addresses
        template variables by name. At most 32 distinct placeholders may appear;
        a larger set is refused at this field.

      - `examples: optional unknown`

        One example value per variable, keyed by variable name. **Required for
        every variable the copy uses** — the channel refuses a template whose
        variables carry no examples, and reviewers read the examples to decide.
        Omit it entirely when the copy has no variables.

        Each value is 1–1024 characters, may not be whitespace-only, and may not
        contain a line break, a tab, or four consecutive spaces, because a
        parameter value carries the same published rules at send time.

    - `buttons: optional array of object { text, type, phone_number, url }`

      - `text: string`

      - `type: "quick_reply" or "phone_number" or "url"`

        - `"quick_reply"`

        - `"phone_number"`

        - `"url"`

      - `phone_number: optional string`

      - `url: optional string`

- `footer: optional string`

  An optional footer line for an ordinary template. Takes no variables. It cannot be combined with `carousel`: a carousel bubble is the body plus its cards and has no footer, so the pair is refused with a fault at `/footer` before anything is recorded and the name remains reusable.

- `media_header: optional object { kind, upload_ref }`

  Optional image, video, or document header for an ordinary template. The referenced bytes are uploaded for template review as part of this submission. It cannot be combined with `authentication` or `carousel`.

  - `kind: "image" or "video" or "document"`

    The media shape reviewed with this template.

    - `"image"`

    - `"video"`

    - `"document"`

  - `upload_ref: string`

    A durable handle returned by `POST /v1/media` for this account and key mode. Template authoring accepts JPEG, PNG, MP4, and PDF up to 5 MiB for every kind. This is this API's conservative authoring limit. The `upload_ref` follows the media route's documented retention posture and is not permanent.

### Returns

- `Template object { category, created_at, language, 9 more }`

  One registered message template in any status, with the parameters it takes.

  A template is identified by its `name` and `language` together — that is the
  pair a `template` part names, and the pair this API addresses it by. There is
  no separate template id.

  - `category: string`

    `utility`, `marketing` or `authentication` — the category review ASSIGNED, which can differ from the one submitted — read it back after approval, since it determines pricing and whether marketing delivery rules apply. Values grow additively.

  - `created_at: string`

  - `language: string`

    The locale this variant is written in, such as `en_US`.

  - `name: string`

    Lowercase letters, digits and underscores. Permanent once submitted.

  - `parameters: unknown`

    The variables this template takes, as a JSON Schema (2020-12) object —
    ready to hand to a model verbatim as a tool schema. Every property is a
    string and every one is required: the channel substitutes all of them.

    Each property publishes `minLength`, `maxLength` and `pattern` alongside
    its example, so a model sees the same value rules the send path enforces:
    values are 1–1024 characters, not whitespace-only, and contain no line
    break, tab or run of four spaces.

    An `authentication` template takes exactly one parameter, `code`: the
    one-time code the message delivers, 1–15 characters. You generate the
    code; it is shown in the message body and is what the code button copies
    or fills in. A longer value is refused, never shortened. The `{{1}}` in
    such a template's `content.body` is where that code appears.

    An empty `properties` object means the template takes no variables.
    `null` means something different and specific: this template's variables
    cannot be addressed by name over this API (they are positional, or they
    live in a part of the template this API does not read yet). A `template`
    part naming such a template cannot supply its variables.

  - `quality: string`

    The channel's quality signal for this template, when it has one. `null` until it does. Free text; treat an unknown value as no signal.

  - `rejected_reason: string`

    Why the template was refused or held, when the channel said. Free text for a human; the wording is not stable and must not be branched on. `null` when there is nothing to say.

  - `send_schema: unknown`

    The complete schema for the `template` part that sends this exact template, including its fixed identity, named parameters, media header, and carousel values. After approval it is published on the registry's next read-back, up to about fifteen minutes later; until then it is `null` and a send is refused with `template_not_sendable` pointing at the template. `null` otherwise means this template cannot be addressed safely through this API.

  - `status: string`

    Where this template stands. Only `approved` may be sent.

    `draft` — a submission attempt has not produced a registered template.
    Consult `retry_allowed`; draft status alone never permits resubmission.
    `pending` — under review. Review time varies: templates have been
    approved in minutes and, in other cases, over several days, so poll this
    read rather than assuming a wait.
    `approved` — sendable. After approval, `send_schema` is published on the
    registry's next read-back, up to about fifteen minutes later.
    `rejected` — refused; `rejected_reason` may say why.
    `paused` — was approved and is currently held, usually for quality. Not
    terminal: it can become `approved` again without you doing anything.
    `disabled` — no longer usable.

    Values grow additively, and anything you do not recognise is not sendable.

  - `updated_at: string`

    When this template's registry facts last changed. An unchanged channel reconciliation does not move it.

  - `content: optional object { body, buttons, carousel, 2 more }`

    Human-readable copy and fixed actions in a registered template.

    - `body: optional string`

    - `buttons: optional array of object { type, phone_number, text, url }`

      - `type: string`

        The action kind; values grow additively. `one_time_code` is an authentication template's code button: it copies or fills in the code each send supplies as the `code` parameter, carries no `url`, and has no send-time slot of its own.

      - `phone_number: optional string`

        A fixed telephone destination baked into the template, when present.

      - `text: optional string`

        The label shown to the recipient, when the channel supplies one.

      - `url: optional string`

        The destination baked into the template, when present. A `url` button's destination may end in `{{1}}`, the variable each send fills.

    - `carousel: optional array of object { body, buttons, header }`

      - `body: optional string`

      - `buttons: optional array of object { type, phone_number, text, url }`

        - `type: string`

          The action kind; values grow additively. `one_time_code` is an authentication template's code button: it copies or fills in the code each send supplies as the `code` parameter, carries no `url`, and has no send-time slot of its own.

        - `phone_number: optional string`

          A fixed telephone destination baked into the template, when present.

        - `text: optional string`

          The label shown to the recipient, when the channel supplies one.

        - `url: optional string`

          The destination baked into the template, when present. A `url` button's destination may end in `{{1}}`, the variable each send fills.

      - `header: optional object { kind, text }`

        A media or text header as it will appear to the recipient.

        - `kind: string`

          `text`, `image`, `video`, or `document`; values grow additively.

        - `text: optional string`

          Fixed header copy. Omitted for media headers.

    - `footer: optional string`

    - `header: optional object { kind, text }`

      A media or text header as it will appear to the recipient.

      - `kind: string`

        `text`, `image`, `video`, or `document`; values grow additively.

      - `text: optional string`

        Fixed header copy. Omitted for media headers.

  - `retry_allowed: optional boolean`

    Whether another submission using this draft's name and language is currently allowed. A corrected definition may be required. Present on all responses from current servers. Clients must not offer retry unless this is explicitly `true`.

### Example

```http
curl https://whatsapp.messages.api.linqapp.com/v1/templates \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_WHATSAPP_API_KEY" \
    -d '{
          "category": "category",
          "language": "language",
          "name": "name"
        }'
```

#### Response

```json
{
  "category": "category",
  "created_at": "2019-12-27T18:11:19.117Z",
  "language": "language",
  "name": "name",
  "parameters": {},
  "quality": "quality",
  "rejected_reason": "rejected_reason",
  "send_schema": {},
  "status": "status",
  "updated_at": "2019-12-27T18:11:19.117Z",
  "content": {
    "body": "body",
    "buttons": [
      {
        "type": "type",
        "phone_number": "phone_number",
        "text": "text",
        "url": "url"
      }
    ],
    "carousel": [
      {
        "body": "body",
        "buttons": [
          {
            "type": "type",
            "phone_number": "phone_number",
            "text": "text",
            "url": "url"
          }
        ],
        "header": {
          "kind": "kind",
          "text": "text"
        }
      }
    ],
    "footer": "footer",
    "header": {
      "kind": "kind",
      "text": "text"
    }
  },
  "retry_allowed": true
}
```
