Skip to content
Linq Copy agent prompt

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

maxLength32
name: string

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

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

One of the following:
"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.

maxLength25
code_expiration_minutes: optional number

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

formatint32
maximum90
minimum1
copy_code_text: optional string

Localized fallback copy-button label, maximum 25 characters.

maxLength25
message_send_ttl_seconds: optional number

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

formatint32
maximum900
minimum30
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.

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

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

maxLength1024
minLength1
type: optional "url" or "quick_reply"

url (the default when omitted) or quick_reply.

One of the following:
"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.

maxLength2000
minLength9
One of the following:
One of the following:
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.

One of the following:
"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.

maxLength256
ReturnsExpand Collapse
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
formatdate-time
language: string

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

maxLength32
name: string

Lowercase letters, digits and underscores. Permanent once submitted.

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

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

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.

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.

Register a template

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"
        }'
{
  "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
}
Returns Examples
{
  "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
}