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 is an unbounded queue and an hour or more is ordinary. Nothing is sendable until it is approved — watch for it on GET /v1/templates.

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 error.type api_error, 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 error.type api_error, 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 asks you to retry the unchanged body. Exhausted preparation answers HTTP 503 template_asset_unavailable. Once the template-create call may have reached the channel but its answer is unknown, HTTP 409 template_outcome_indeterminate 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.

language: string

The locale this variant is written in, such as en_US. One template name may have several language variants, each registered separately.

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, url }

One or two fixed URL buttons for an ordinary template. Their non-blank labels and HTTPS destinations are baked into the reviewed template and take no send-time values.

text: string

Visible label, maximum 25 characters.

maxLength25
minLength1
url: string

Fixed HTTPS destination baked into the approved template. Senders cannot replace it.

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. Private-beta 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 beta 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 the channel ASSIGNED, which may not be the one that was requested: a template that reads as marketing is recategorised on review, and the category is what a send is priced at. 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. null 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 is an unbounded queue; an hour or more is ordinary for a first submission. approved — sendable. 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
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

A fixed destination baked into the template, when present.

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
}