Templates

Every request sends x-api-key. New here? Start with the quickstart, or read API keys.

OperationSDK callEndpoint
List templatestemplates.listGET /v1/templates/
Create a templatetemplates.createPOST /v1/templates/
Get a templatetemplates.getGET /v1/templates/{id}
Update a templatetemplates.updatePATCH /v1/templates/{id}
Validate a templatetemplates.validatePOST /v1/templates/validate

List templates

List this account's utility templates, newest change first. Pass status and updatedAfter to poll for just what moved since your last call, and starting_after with the last id to page. Marketing templates are not listed, since /v1/messages will not send them.

await msg.templates.list({
  limit: 20,
  startingAfter: "tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e",
});

Query parameters

FieldTypeRequiredDescription
limitintegernoHow many templates per page.
starting_afterstringnoThe last id from the previous page. Results continue after it.
statuspending | processing | approved | rejectednoReturn only templates in this state.
updatedAfterobjectnoOnly templates changed since this instant, which is what makes polling cheap.

Response 200

FieldTypeDescription
dataobject[]This page of templates.
has_morebooleanAnother page exists. Pass the last id in data as starting_after.

Errors

Returns 400, 401, 403, 429, 503. Every one carries the standard envelope — branch on code, not the status. See Errors.

Create a template

Create a utility template and submit it to Meta in one call. There is no draft state. Returns processing; poll GET /v1/templates/\{id\} until it is approved or rejected, then send with the returned tpl_ id. A media header takes a med_ id from POST /v1/media/. Requires an Idempotency-Key, since a create permanently claims the name on your WhatsApp Business Account.

await msg.templates.create({
  name: "order_shipped",
  category: "UTILITY",
  language: "en_US",
  body: "Hi {{1}}, your order {{2}} has shipped.",
});

Body

FieldTypeRequiredDescription
namestringyesLowercase letters, digits and underscores. Claimed permanently at Meta — a deleted name stays reserved for about 30 days.
categoryUTILITYyesUtility only. Marketing templates cannot be created, listed or sent through the API.
languagestringyesMeta's language code, not ours.
bodystringyesThe message text. Use \{\{1\}\}, \{\{2\}\} for placeholders.
variablesobject[]noAn example value per placeholder. Meta rejects a template whose examples are missing.
headerobjectnoA text or media header. Omit it entirely for no header.
footerstringnoSmall text under the body.

Response 201

Example response
{
  "id": "tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e",
  "status": "pending",
  "category": "MARKETING"
}
FieldTypeDescription
idstringPass this to POST /v1/messages as templateId.
namestringThe name this template holds at Meta.
statuspending | processing | approved | rejectedOnly an approved template can be sent. pending and processing are waiting on Meta; rejected carries a rejectionReason.
categoryMARKETING | UTILITYMeta re-categorises on approval, so this can differ from what was submitted.
languagestringMeta's language code.
bodystringThe message text, with \{\{n\}\} placeholders.
variablesobject[]One entry per placeholder in the body.
headerobjectNull when the template has no header.
footerstringNull when the template has no footer.
rejectionReasonstringWhy Meta refused it. Null unless status is rejected.
approvedAtstring (ISO 8601)Null until Meta approves it.
createdAtstring (ISO 8601)When the template was created here.
updatedAtstring (ISO 8601)Last change, including a status change from Meta.

Errors

Returns 400, 401, 403, 404, 409, 429, 502, 503. Every one carries the standard envelope — branch on code, not the status. See Errors.

Get a template

Fetch one template's approval status. processing means Meta is still reviewing; rejected carries the reason. Covers any template on the account, including one Meta re-classified as marketing, which the list omits.

await msg.templates.get("tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e");

Path parameters

FieldTypeRequiredDescription
idstringyesA tpl_ id from GET /v1/templates.

Response 200

Example response
{
  "id": "tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e",
  "status": "pending",
  "category": "MARKETING"
}
FieldTypeDescription
idstringPass this to POST /v1/messages as templateId.
namestringThe name this template holds at Meta.
statuspending | processing | approved | rejectedOnly an approved template can be sent. pending and processing are waiting on Meta; rejected carries a rejectionReason.
categoryMARKETING | UTILITYMeta re-categorises on approval, so this can differ from what was submitted.
languagestringMeta's language code.
bodystringThe message text, with \{\{n\}\} placeholders.
variablesobject[]One entry per placeholder in the body.
headerobjectNull when the template has no header.
footerstringNull when the template has no footer.
rejectionReasonstringWhy Meta refused it. Null unless status is rejected.
approvedAtstring (ISO 8601)Null until Meta approves it.
createdAtstring (ISO 8601)When the template was created here.
updatedAtstring (ISO 8601)Last change, including a status change from Meta.

Errors

Returns 400, 401, 403, 404, 429, 503. Every one carries the standard envelope — branch on code, not the status. See Errors.

Update a template

Fix a template Meta rejected and resubmit it. Only a pending or rejected template may be edited — Meta freezes content once a template is approved or in review, so anything else is a 409. Omitted fields keep their current value.

await msg.templates.update("tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e", {
  name: "order_shipped",
  language: "en_US",
  body: "Hi {{1}}, your order {{2}} has shipped.",
});

Path parameters

FieldTypeRequiredDescription
idstringyesA tpl_ id from GET /v1/templates.

Body

FieldTypeRequiredDescription
namestringnoLowercase letters, digits and underscores. Claimed permanently at Meta — a deleted name stays reserved for about 30 days.
categoryUTILITYnoUtility only. Marketing templates cannot be created, listed or sent through the API.
languagestringnoMeta's language code, not ours.
bodystringnoThe message text. Use \{\{1\}\}, \{\{2\}\} for placeholders.
variablesobject[]noAn example value per placeholder. Meta rejects a template whose examples are missing.
headerobjectnoA text or media header. Omit it entirely for no header.
footerstringnoSmall text under the body.

Response 200

Example response
{
  "id": "tpl_3b9f1c2d4e5a6b7c8d9e0f1a2b3c4d5e",
  "status": "pending",
  "category": "MARKETING"
}
FieldTypeDescription
idstringPass this to POST /v1/messages as templateId.
namestringThe name this template holds at Meta.
statuspending | processing | approved | rejectedOnly an approved template can be sent. pending and processing are waiting on Meta; rejected carries a rejectionReason.
categoryMARKETING | UTILITYMeta re-categorises on approval, so this can differ from what was submitted.
languagestringMeta's language code.
bodystringThe message text, with \{\{n\}\} placeholders.
variablesobject[]One entry per placeholder in the body.
headerobjectNull when the template has no header.
footerstringNull when the template has no footer.
rejectionReasonstringWhy Meta refused it. Null unless status is rejected.
approvedAtstring (ISO 8601)Null until Meta approves it.
createdAtstring (ISO 8601)When the template was created here.
updatedAtstring (ISO 8601)Last change, including a status change from Meta.

Errors

Returns 400, 401, 403, 404, 409, 429, 502, 503. Every one carries the standard envelope — branch on code, not the status. See Errors.

Validate a template

Check a template against our content rules without creating it. Nothing is stored and Meta is not called, so a test key may use it. Returns every issue at once. Passing here does not guarantee Meta will approve — that review is separate.

await msg.templates.validate({
  name: "order_shipped",
  category: "UTILITY",
  language: "en_US",
  body: "Hi {{1}}, your order {{2}} has shipped.",
});

Body

FieldTypeRequiredDescription
namestringyesLowercase letters, digits and underscores. Claimed permanently at Meta — a deleted name stays reserved for about 30 days.
categoryUTILITYyesUtility only. Marketing templates cannot be created, listed or sent through the API.
languagestringyesMeta's language code, not ours.
bodystringyesThe message text. Use \{\{1\}\}, \{\{2\}\} for placeholders.
variablesobject[]noAn example value per placeholder. Meta rejects a template whose examples are missing.
headerobjectnoA text or media header. Omit it entirely for no header.
footerstringnoSmall text under the body.

Response 200

FieldTypeDescription
validbooleanWhether the template would be accepted for submission.
issuesobject[]Every problem at once, so a template is fixable in one round trip. Empty when valid.

Errors

Returns 400, 401, 403, 409, 429, 503. Every one carries the standard envelope — branch on code, not the status. See Errors.