/v1/templates/{template_id}betaEdit 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 — every check runs with ?dry_run=true, nothing changes
- Confirm
- Header
Api-Confirm: submit - Undo
- Edit it again with the old content (it is reviewed again).
Replaces a template's content (header, body, footer, buttons) and resubmits it to WhatsApp for review: it goes back to PENDING and cannot be sent until approved again, so it needs Api-Confirm: submit.
nameandlanguagemust stay the same as the template's; to change them, create a new template.- Only
APPROVED,REJECTEDorPAUSEDtemplates can be edited (409,reason: not_editable, otherwise). WhatsApp limits how often an approved template can be edited, and never lets its category change. - A draft that breaks a rule is refused with
400(reason: template_rules).
Try it with ?dry_run=true: the template is read and the draft checked, nothing is submitted.
Try it
Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.
Code and response
curl -X PUT 'https://mcp.wa-api.cloud/v1/templates/9120?dry_run=true' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"template": {
"name": "order_shipped",
"language": "en_US",
"category": "UTILITY",
"body": {
"text": "Hi {{1}}, good news: your order {{2}} has shipped.",
"examples": [
"Jane",
"#1001"
]
}
}
}'The code reads your key from $API_KEY.
Parameters
| Field | Type | What it is |
|---|---|---|
| template_idrequired | string | integer · path | The template id.Signed in? Pick one from your data with “My data”. |
Body
| Field | Type | What it is |
|---|---|---|
| templaterequired | object | The new content. name and language must match the template's. |
| namerequired | string | Template name: lowercase letters, digits, underscores (e.g. order_shipped_v2).0–1000 characters |
| languagerequired | string | Meta language code, e.g. en, en_US, ar, hi, pt_BR.0–20 characters |
| categoryrequired | string | MARKETING | UTILITY | AUTHENTICATION.0–40 characters |
| header | object | Optional header. |
| formatrequired | string | TEXT | IMAGE | VIDEO | DOCUMENT | LOCATION.one of: TEXT, IMAGE, VIDEO, DOCUMENT, LOCATION |
| text | string | TEXT header: ≤ 60 chars, at most one {{1}}.0–1000 characters |
| example | string | TEXT header with {{1}}: its sample value.0–1000 characters |
| media_handle | string | IMAGE/VIDEO/DOCUMENT header: the Meta upload handle used as the sample (POST /v1/templates/samples).0–4000 characters |
| body | object | Body.default {} |
| text | string | Body text (≤ 1024 chars) with {{1}}, {{2}} … variables. Omit for AUTHENTICATION (Meta fixes it).0–5000 characters |
| examples | array of string | One sample per body variable, in order ({{1}} first).0–100 items |
| add_security_recommendation | boolean | AUTHENTICATION only: append "For your security, do not share this code." |
| footer | object | Optional footer. |
| text | string | Footer text (≤ 60 chars, no variables).0–1000 characters |
| code_expiration_minutes | integer | AUTHENTICATION only: code expiry shown in the footer (1-90).-9007199254740991–9007199254740991 |
| buttons | array of object | Buttons in display order (≤ 10).0–30 items |
| typerequired | string | QUICK_REPLY | URL | PHONE_NUMBER | COPY_CODE | FLOW | VOICE_CALL | OTP.one of: QUICK_REPLY, URL, PHONE_NUMBER, COPY_CODE, FLOW, VOICE_CALL, OTP |
| text | string | Button label (≤ 25 chars). Not used for COPY_CODE.0–200 characters |
| url | string | URL buttons: https URL; for a dynamic URL end it with {{1}} (e.g. https://shop.example/track/{{1}}).0–4000 characters |
| url_example | string | Dynamic URL buttons: a full example URL with the variable filled in.0–4000 characters |
| phone_number | string | PHONE_NUMBER buttons: E.164 number, e.g. +971501234567.0–64 characters |
| coupon_code | string | COPY_CODE buttons: the coupon code to copy (≤ 20 chars).0–64 characters |
| flow_id | string | FLOW buttons: the WhatsApp flow id.0–64 characters |
| flow_action | string | FLOW buttons: NAVIGATE (default) or DATA_EXCHANGE for endpoint flows.one of: NAVIGATE, DATA_EXCHANGE |
| navigate_screen | string | FLOW buttons with NAVIGATE: the first screen id.0–200 characters |
| otp_type | string | OTP buttons: COPY_CODE (default), ONE_TAP or ZERO_TAP.one of: COPY_CODE, ONE_TAP, ZERO_TAP |
| supported_apps | array of object | ONE_TAP / ZERO_TAP OTP buttons: your Android app(s).0–5 items |
| package_namerequired | string | Android package name.0–255 characters |
| signature_hashrequired | string | App signing key hash.0–64 characters |
| limited_time_offer | object | Limited-time offer (MARKETING; needs a COPY_CODE button). |
| textrequired | string | Offer text shown on the chip (≤ 16 chars).0–200 characters |
| has_expiration | boolean | Show a countdown (the expiry is set when sending). |
| carousel | object | Media carousel (MARKETING): the body is the message above the cards; no top-level header, footer or buttons. |
| cardsrequired | array of object | 2-10 cards.0–20 items |
| header_formatrequired | string | Card media type (the same on every card).one of: IMAGE, VIDEO |
| media_handlerequired | string | Meta upload handle for the card media (POST /v1/templates/samples).0–4000 characters |
| body | object | Card text. |
| buttonsrequired | array of object | 1-2 buttons (QUICK_REPLY, URL, PHONE_NUMBER), the same layout on every card.0–5 items |
Headers
| Field | Type | What it is |
|---|---|---|
| Authorizationrequired | header | Bearer $API_KEY — your API key. |
| Api-Version | header | The 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-Key | header | Any unique string (8–128 characters). A retry with the same key returns the first answer instead of running twice. Kept 24 hours.pattern ^[A-Za-z0-9._:-]+$ · 8–128 characters |
| Api-Confirmrequired | header | Type 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 200
Resubmitted for review.
| Field | Type | What it is |
|---|---|---|
| idrequired | string | |
| namerequired | string | |
| statusrequired | string | |
| lintrequired | object | |
| validrequired | boolean | true when there are no errors (warnings may remain). |
| errorsrequired | number | |
| warningsrequired | number | |
| issuesrequired | array of object | |
| rulerequired | string | |
| severityrequired | string | one of: error, warning, info |
| pathrequired | string | |
| messagerequired | string | |
| fixrequired | string | |
| variablesrequired | object | |
| headerrequired | number | |
| bodyrequired | number | |
| previewrequired | string or null | Body with samples filled in. |
| dry_run | boolean | true when this was a dry run: every check ran and nothing changed. |
Errors
Errors are application/problem+json. Branch on code.
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | A field is missing or has the wrong format. |
| 401 | unauthenticated | The Authorization header is missing, the key is unknown, expired or revoked. |
| 403 | entitlement_required | The workspace's plan does not include API access ( |
| 403 | insufficient_scope | The key does not have the permission this operation needs. |
| 404 | not_found | No template with this id. |
| 409 | conflict | The template is under review or otherwise not editable ( |
| 428 | confirm_required | Send the header |
| 429 | rate_limited | The key or workspace went over its rate limit. Wait for |
| 502 | upstream_error | WhatsApp refused the edit. |
| 503 | upstream_unavailable | A service behind the API is briefly unavailable. Safe to retry with backoff. |
| 504 | timeout | The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice. |
Examples
Change the body wording
Request body
{
"template": {
"name": "order_shipped",
"language": "en_US",
"category": "UTILITY",
"body": {
"text": "Hi {{1}}, good news: your order {{2}} has shipped.",
"examples": [
"Jane",
"#1001"
]
}
}
}Response 200
{
"id": "9120",
"name": "order_shipped",
"status": "PENDING",
"lint": {
"valid": true,
"errors": 0,
"warnings": 0,
"issues": [],
"variables": {
"header": 0,
"body": 2
},
"preview": null
}
}Operation path
The same operation is also at POST /v1/ops/messaging_update_template, with every field in the JSON body.