Skip to content
API PlatformDevelopers
POST/v1/templatesbeta

Create a WhatsApp template

Needs
Send WhatsApp messages; manage templates, quick messages and WhatsApp short links (messaging:write)
Plan
Any plan with API access
Limits
60 a minute per key
Dry run
Yes — a full preview with ?dry_run=true
Confirm
Header Api-Confirm: submit
Undo
A submission cannot be withdrawn. Archive the template with Archive templates once it is reviewed.

Checks a template draft against WhatsApp's rules, then submits it to WhatsApp for review. The submission is visible to WhatsApp and counts against your account's template limits, so it needs Api-Confirm: submit. Review takes minutes to hours: read the template with Get a template until it is APPROVED or REJECTED.

  • name: lower-case letters, digits and _; the same name can exist once per language. A name that exists in that language is refused with 409 (reason: duplicate).
  • language: a WhatsApp language code such as en, en_US or pt_BR.
  • A draft that breaks a rule is refused with 400 (reason: template_rules); errors[] names each field and rule. Check a template draft explains every rule with a fix.
  • An IMAGE, VIDEO or DOCUMENT header needs a sample handle from Upload a template header sample.

Run it with ?dry_run=true first: every check runs and payload_preview shows exactly what would be submitted, but nothing is.

Try it

Body
template *

The draft: name, language, category, optional header, body, footer and buttons.

Template name: lowercase letters, digits, underscores (e.g. order_shipped_v2).

Meta language code, e.g. en, en_US, ar, hi, pt_BR.

MARKETING | UTILITY | AUTHENTICATION.

header

Optional header.

TEXT | IMAGE | VIDEO | DOCUMENT | LOCATION.

TEXT header: ≤ 60 chars, at most one {{1}}.

TEXT header with {{1}}: its sample value.

IMAGE/VIDEO/DOCUMENT header: the Meta upload handle used as the sample (`POST /v1/templates/samples`).

body

Body.

Body text (≤ 1024 chars) with {{1}}, {{2}} … variables. Omit for AUTHENTICATION (Meta fixes it).

One sample per body variable, in order ({{1}} first).

AUTHENTICATION only: append "For your security, do not share this code."

footer

Optional footer.

Buttons in display order (≤ 10).

limited_time_offer

Limited-time offer (MARKETING; needs a COPY_CODE button).

Offer text shown on the chip (≤ 16 chars).

Show a countdown (the expiry is set when sending).

carousel

Media carousel (MARKETING): the body is the message above the cards; no top-level header, footer or buttons.

The WhatsApp account to create it in. Needed only when your workspace has several.

Instead of `business_account_id`: a connected WhatsApp channel; its account is used.

Let WhatsApp change the category instead of rejecting the template (default `true`).

Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.

Test mode (dry run): nothing will change
Turns test mode off for this page only. It switches back when you leave the page.

Code and response

curl -X POST 'https://mcp.wa-api.cloud/v1/templates?dry_run=true' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "template": {
    "name": "order_ready",
    "language": "en_US",
    "category": "UTILITY",
    "body": {
      "text": "Hi {{1}}, your order {{2}} is ready for pickup today.",
      "examples": [
        "Jane",
        "#1001"
      ]
    },
    "footer": {
      "text": "Reply STOP to opt out"
    },
    "buttons": [
      {
        "type": "QUICK_REPLY",
        "text": "On my way"
      }
    ]
  }
}'

The code reads your key from $API_KEY.

Body

Body fields
FieldTypeWhat it is
templaterequiredobjectThe draft: name, language, category, optional header, body, footer and buttons.
namerequiredstringTemplate name: lowercase letters, digits, underscores (e.g. order_shipped_v2).0–1000 characters
languagerequiredstringMeta language code, e.g. en, en_US, ar, hi, pt_BR.0–20 characters
categoryrequiredstringMARKETING | UTILITY | AUTHENTICATION.0–40 characters
headerobjectOptional header.
formatrequiredstringTEXT | IMAGE | VIDEO | DOCUMENT | LOCATION.one of: TEXT, IMAGE, VIDEO, DOCUMENT, LOCATION
textstringTEXT header: ≤ 60 chars, at most one {{1}}.0–1000 characters
examplestringTEXT header with {{1}}: its sample value.0–1000 characters
media_handlestringIMAGE/VIDEO/DOCUMENT header: the Meta upload handle used as the sample (POST /v1/templates/samples).0–4000 characters
bodyobjectBody.default {}
textstringBody text (≤ 1024 chars) with {{1}}, {{2}} … variables. Omit for AUTHENTICATION (Meta fixes it).0–5000 characters
examplesarray of stringOne sample per body variable, in order ({{1}} first).0–100 items
add_security_recommendationbooleanAUTHENTICATION only: append "For your security, do not share this code."
footerobjectOptional footer.
textstringFooter text (≤ 60 chars, no variables).0–1000 characters
code_expiration_minutesintegerAUTHENTICATION only: code expiry shown in the footer (1-90).-9007199254740991–9007199254740991
buttonsarray of objectButtons in display order (≤ 10).0–30 items
typerequiredstringQUICK_REPLY | URL | PHONE_NUMBER | COPY_CODE | FLOW | VOICE_CALL | OTP.one of: QUICK_REPLY, URL, PHONE_NUMBER, COPY_CODE, FLOW, VOICE_CALL, OTP
textstringButton label (≤ 25 chars). Not used for COPY_CODE.0–200 characters
urlstringURL buttons: https URL; for a dynamic URL end it with {{1}} (e.g. https://shop.example/track/{{1}}).0–4000 characters
url_examplestringDynamic URL buttons: a full example URL with the variable filled in.0–4000 characters
phone_numberstringPHONE_NUMBER buttons: E.164 number, e.g. +971501234567.0–64 characters
coupon_codestringCOPY_CODE buttons: the coupon code to copy (≤ 20 chars).0–64 characters
flow_idstringFLOW buttons: the WhatsApp flow id.0–64 characters
flow_actionstringFLOW buttons: NAVIGATE (default) or DATA_EXCHANGE for endpoint flows.one of: NAVIGATE, DATA_EXCHANGE
navigate_screenstringFLOW buttons with NAVIGATE: the first screen id.0–200 characters
otp_typestringOTP buttons: COPY_CODE (default), ONE_TAP or ZERO_TAP.one of: COPY_CODE, ONE_TAP, ZERO_TAP
supported_appsarray of objectONE_TAP / ZERO_TAP OTP buttons: your Android app(s).0–5 items
package_namerequiredstringAndroid package name.0–255 characters
signature_hashrequiredstringApp signing key hash.0–64 characters
limited_time_offerobjectLimited-time offer (MARKETING; needs a COPY_CODE button).
textrequiredstringOffer text shown on the chip (≤ 16 chars).0–200 characters
has_expirationbooleanShow a countdown (the expiry is set when sending).
carouselobjectMedia carousel (MARKETING): the body is the message above the cards; no top-level header, footer or buttons.
cardsrequiredarray of object2-10 cards.0–20 items
header_formatrequiredstringCard media type (the same on every card).one of: IMAGE, VIDEO
media_handlerequiredstringMeta upload handle for the card media (POST /v1/templates/samples).0–4000 characters
bodyobjectCard text.
buttonsrequiredarray of object1-2 buttons (QUICK_REPLY, URL, PHONE_NUMBER), the same layout on every card.0–5 items
business_account_idstringThe WhatsApp account to create it in. Needed only when your workspace has several.0–64 characters
channel_idstringInstead of business_account_id: a connected WhatsApp channel; its account is used.0–64 charactersSigned in? Pick one from your data with “My data”.
allow_category_changebooleanLet WhatsApp change the category instead of rejecting the template (default true).

Headers

Headers
FieldTypeWhat it is
AuthorizationrequiredheaderBearer $API_KEY — your API key.
Api-VersionheaderThe API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$
Idempotency-KeyrequiredheaderRequired here. Any unique string (8–128 characters), e.g. your order id plus the step. Kept 24 hours. Not needed with dry_run=true.pattern ^[A-Za-z0-9._:-]+$ · 8–128 characters
Api-ConfirmrequiredheaderType the operation's verb (e.g. delete, submit) to confirm a change that cannot be undone or that WhatsApp or your customers see. Not needed with dry_run=true.

Response 201

Submitted for review (or, on a dry run, the checked draft and the exact payload).

Response fields
FieldTypeWhat it is
submittedrequiredbooleantrue when the template was sent to Meta for review.
idrequiredstring or nullTemplate id when submitted.
namerequiredstring
languagerequiredstring
categoryrequiredstring or null
statusrequiredstring or null
business_account_idrequiredstring
business_account_namerequiredstringThe account, by name — say it to the user.
lintrequiredobject
validrequiredbooleantrue when there are no errors (warnings may remain).
errorsrequirednumber
warningsrequirednumber
issuesrequiredarray of object
rulerequiredstring
severityrequiredstringone of: error, warning, info
pathrequiredstring
messagerequiredstring
fixrequiredstring
variablesrequiredobject
headerrequirednumber
bodyrequirednumber
previewrequiredstring or nullBody with samples filled in.
payload_previewrequiredarray of objectThe exact components that are (or would be) submitted.
dry_runbooleantrue when this was a dry run: every check ran and nothing changed.

Errors

Errors are application/problem+json. Branch on code.

StatusCodeWhen
400invalid_input

A field is missing or has the wrong format. errors[] points at each field. The draft breaks a template rule (reason: template_rules), or the workspace has several WhatsApp accounts and none was chosen (reason: account_ambiguous). Also returned when the Idempotency-Key header is missing, or was used before with a different body.

401unauthenticated

The Authorization header is missing, the key is unknown, expired or revoked.

403entitlement_required

The workspace's plan does not include API access (api_access).

403insufficient_scope

The key does not have the permission this operation needs.

404not_found

The channel or WhatsApp account does not exist in your workspace.

409conflict

A template with this name already exists in this language (reason: duplicate), or no WhatsApp account is connected (reason: channel_not_connected). Also returned while a request with the same Idempotency-Key is still running.

428confirm_required

Send the header Api-Confirm: submit to confirm. Not needed with dry_run=true.

429rate_limited

The key or workspace went over its rate limit. Wait for Retry-After seconds.

502upstream_error

WhatsApp refused the submission.

503upstream_unavailable

A service behind the API is briefly unavailable. Safe to retry with backoff.

504timeout

The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice.

Examples

An order-ready utility template

Request body

{
  "template": {
    "name": "order_ready",
    "language": "en_US",
    "category": "UTILITY",
    "body": {
      "text": "Hi {{1}}, your order {{2}} is ready for pickup today.",
      "examples": [
        "Jane",
        "#1001"
      ]
    },
    "footer": {
      "text": "Reply STOP to opt out"
    },
    "buttons": [
      {
        "type": "QUICK_REPLY",
        "text": "On my way"
      }
    ]
  }
}

Response 201

{
  "submitted": false,
  "id": null,
  "name": "order_ready",
  "language": "en_US",
  "category": "UTILITY",
  "status": null,
  "business_account_id": "4410",
  "business_account_name": "Main WhatsApp",
  "lint": {
    "valid": true,
    "errors": 0,
    "warnings": 0,
    "issues": [],
    "variables": {
      "header": 0,
      "body": 2
    },
    "preview": null
  },
  "payload_preview": [
    {
      "type": "BODY",
      "text": "Hi {{1}}, your order {{2}} is ready for pickup today.",
      "example": {
        "body_text": [
          [
            "Jane",
            "#1001"
          ]
        ]
      }
    },
    {
      "type": "FOOTER",
      "text": "Reply STOP to opt out"
    },
    {
      "type": "BUTTONS",
      "buttons": [
        {
          "type": "QUICK_REPLY",
          "text": "On my way"
        }
      ]
    }
  ],
  "dry_run": true
}

Operation path

The same operation is also at POST /v1/ops/messaging_create_template, with every field in the JSON body.