Create a template
Authorization
AuthorizationheaderrequiredBearer YOUR_API_KEY. Create a key in the dashboard.
Body
application/json
namestringrequiredlanguagestringrequiredDefaultenbodystringrequiredDefault template body. Used when no channel-specific body is set.
smsBodystringChannel-specific body for SMS. Falls back to
bodyif not set.telegramBodystringChannel-specific body for Telegram. Falls back to
bodyif not set.instagramBodystringChannel-specific body for Instagram. Falls back to
bodyif not set.whatsappCategorystringWhatsApp template category.
One ofUTILITYMARKETINGAUTHENTICATIONheaderTypestringType of header for the template.
One oftextimagevideodocumentheaderContentstringHeader content (text string or media URL).
footerstringFooter text for the template.
Limitsmax length 60variablesstring[]buttonsobject[]Template buttons (max 3).
Limitsmax items 38 properties
typestringrequiredrequest_contact_inforenders a fixed Share Contact Info button that asks the recipient to share their phone number — useful when a contact adopted a WhatsApp username and you only know their BSUID. It takes no other fields.One ofquick_replyurlphoneotprequest_contact_infotextstringButton label. Required for every type except
request_contact_info, whose label is fixed by WhatsApp.Limitsmax length 25urlstring<uri>Button destination. Use
{{1}}exactly once for a dynamic URL (e.g.https://example.com/orders/{{1}}); WhatsApp only accepts the strict{{1}}form. Static URLs must not contain any{{...}}placeholder.examplestringSample value Meta uses to review templates with a dynamic URL button. Substituted into
{{1}}of the URL when the template is submitted to Meta. Only meaningful whenurlcontains{{1}}; ignored for static URLs.phoneNumberstringotpTypestringRequired when type is 'otp'. COPY_CODE shows copy button, ONE_TAP enables Android autofill.
One ofCOPY_CODEONE_TAPpackageNamestringAndroid package name. Required for ONE_TAP buttons.
signatureHashstringAndroid app signature hash. Required for ONE_TAP buttons.
addSecurityRecommendationbooleanAdd 'Do not share this code' disclaimer. Only for AUTHENTICATION templates.
codeExpirationMinutesintegerCode expiration time in minutes. Only for AUTHENTICATION templates.
Limitsmin 1 · max 90
Responses
201Created as `draft`.
idstringrequirednamestringrequiredTemplate name. For WhatsApp, must match the approved template name in Meta.
languagestringrequiredLanguage code.
bodystringrequiredDefault template body with variables: positional ({{1}}, {{2}}) or named ({{customer_name}}, {{contact.first_name}}). Templates created in Zavu are submitted to Meta as positional; templates imported from a WhatsApp Business Account keep their original format (named or positional). Used when no channel-specific body is set.
smsBodystringChannel-specific body for SMS messages. Falls back to
bodyif not set.telegramBodystringChannel-specific body for Telegram messages. Falls back to
bodyif not set.instagramBodystringChannel-specific body for Instagram messages. Falls back to
bodyif not set.categorystringrequiredThe category the template is billed under. For a template linked to WhatsApp this is the category Meta currently assigns, not the one it was submitted with: Meta can recategorize a template at approval or at any point afterwards, and that changes what the message costs.
A recategorization reaches this field through the
template.status_changedwebhook, which carriesdata.category, and throughPOST /v1/templates/sync, which reconciles it against Meta. Read one of those rather than assuming the category you sent is the one in force.For a template that was never submitted to WhatsApp, this is the category stored locally, defaulting to
UTILITY.One ofUTILITYMARKETINGAUTHENTICATIONstatusstringOne ofdraftpendingapprovedrejectedDefaultdraftvariablesstring[]List of variable names for documentation.
headerTypestringType of header (text, image, video, document).
headerContentstringHeader content (text or media URL).
footerstringFooter text for the template.
buttonsobject[]Template buttons.
8 properties
typestringOne ofquick_replyurlphoneotprequest_contact_infotextstringurlstringexamplestringSample value used to substitute
{{1}}in the URL when submitting the template to Meta for review. Only present for dynamic URL buttons.phoneNumberstringotpTypestringOTP button type. Required when type is 'otp'.
One ofCOPY_CODEONE_TAPpackageNamestringAndroid package name. Required for ONE_TAP buttons.
signatureHashstringAndroid app signature hash. Required for ONE_TAP buttons.
addSecurityRecommendationbooleanAdd 'Do not share this code' disclaimer. Only for AUTHENTICATION templates.
codeExpirationMinutesintegerCode expiration time in minutes. Only for AUTHENTICATION templates.
Limitsmin 1 · max 90whatsappobjectWhatsApp-specific template information.
3 properties
templateNamestringWhatsApp template name.
namespacestringWhatsApp Business Account namespace.
statusstringWhatsApp approval status.
createdAtstring<date-time>updatedAtstring<date-time>
400Invalid template.
codestringrequiredmessagestringrequireddetailsobjectMachine-readable context. Keys that mean the same thing everywhere: -
errors: present on every400 invalid_requestfor an invalid body or query parameter,[{path, message, ...}],pathnaming the field (["to"],["contacts", 2, "recipient"],["limit"]). -recommendation: one sentence saying what to do next. -dashboardUrl: the dashboard screen that fixes it. -connect: on409 no_channel, the channels this project can connect to reach the recipient, each{type, request, note, available, dashboardUrl?};requestis the exact call (POST /v2/channelswith its body, orPOST /v2/numbers), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger).available: falsesays why innote(one-way SMS closed until business verification is approved). -address/from: the address the error is about, under the name the request used for it.1 properties
errorsobject[]3 properties
pathobject[]requiredmessagestringrequiredcodestring
401Unauthorized.
codestringrequiredmessagestringrequireddetailsobjectMachine-readable context. Keys that mean the same thing everywhere: -
errors: present on every400 invalid_requestfor an invalid body or query parameter,[{path, message, ...}],pathnaming the field (["to"],["contacts", 2, "recipient"],["limit"]). -recommendation: one sentence saying what to do next. -dashboardUrl: the dashboard screen that fixes it. -connect: on409 no_channel, the channels this project can connect to reach the recipient, each{type, request, note, available, dashboardUrl?};requestis the exact call (POST /v2/channelswith its body, orPOST /v2/numbers), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger).available: falsesays why innote(one-way SMS closed until business verification is approved). -address/from: the address the error is about, under the name the request used for it.1 properties
errorsobject[]3 properties
pathobject[]requiredmessagestringrequiredcodestring
409`conflict`: this project already has a template with that name.
codestringrequiredmessagestringrequireddetailsobjectMachine-readable context. Keys that mean the same thing everywhere: -
errors: present on every400 invalid_requestfor an invalid body or query parameter,[{path, message, ...}],pathnaming the field (["to"],["contacts", 2, "recipient"],["limit"]). -recommendation: one sentence saying what to do next. -dashboardUrl: the dashboard screen that fixes it. -connect: on409 no_channel, the channels this project can connect to reach the recipient, each{type, request, note, available, dashboardUrl?};requestis the exact call (POST /v2/channelswith its body, orPOST /v2/numbers), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger).available: falsesays why innote(one-way SMS closed until business verification is approved). -address/from: the address the error is about, under the name the request used for it.1 properties
errorsobject[]3 properties
pathobject[]requiredmessagestringrequiredcodestring