Skip to content
API PlatformDevelopers
POST/v1/templates/lintbeta

Check a template draft

Needs
Read your data (mcp:read)
Plan
Any plan with API access
Limits
300 a minute per key
Undo
Nothing to undo: nothing is saved.

Checks a template draft against WhatsApp's rules — names, categories, lengths, variables and buttons — without saving or submitting anything. Every issue comes with a fix.

Try it

Body
template *

A template draft.

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.

This only reads. It uses your real data and changes nothing.

Code and response

curl -X POST 'https://mcp.wa-api.cloud/v1/templates/lint' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "template": {
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "body": {
      "text": "Hi {{1}}, your order {{2}} is on its way.",
      "examples": [
        "Jane"
      ]
    }
  }
}'

The code reads your key from $API_KEY.

Body

Body fields
FieldTypeWhat it is
templaterequiredobjectA template draft.
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

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}$

Response 200

The result of the check.

Response fields
FieldTypeWhat it is
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.

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.

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.

429rate_limited

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

503upstream_unavailable

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

Examples

A draft with a missing sample

Request body

{
  "template": {
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "body": {
      "text": "Hi {{1}}, your order {{2}} is on its way.",
      "examples": [
        "Jane"
      ]
    }
  }
}

Response 200

{
  "valid": false,
  "errors": 1,
  "warnings": 0,
  "issues": [
    {
      "rule": "body.samples",
      "severity": "error",
      "path": "body.examples",
      "message": "The body has 2 variables but 1 sample.",
      "fix": "Add a sample for {{2}}, e.g. \"#1001\"."
    }
  ],
  "variables": {
    "header": 0,
    "body": 2
  },
  "preview": null
}

Operation path

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