A BotoZap guarda um catálogo dos templates de cada conexão: os criados por aqui e os importados da Meta. É esse catálogo que as rotas abaixo leem. Para enviar um template (com variáveis, mídia e botões), veja Enviar mensagens — o envio usa o nome e o idioma e não depende do catálogo: um template aprovado direto no WhatsApp Manager sai mesmo antes de ser importado.
Listar templates
GET /api/v1/templates (escopo templates:read) devolve os templates da conta, mais recentes primeiro, paginados por página: page e per_page (até 100, padrão 20), com o total em meta e nos cabeçalhos X-Total, X-Total-Pages, X-Per-Page e X-Page.
GET /api/v1/templates?status=approved&category=utility&per_page=50
HTTP/1.1 200 OK
X-Total: 12
X-Total-Pages: 1
{
"data": [
{
"id": "8b0f6a52-1c3e-4d7a-9e21-5f4c3b2a1d09",
"name": "pedido_enviado",
"language": "pt_BR",
"category": "UTILITY",
"status": "APPROVED",
"rejection_reason": null,
"meta_template_id": "1029384756473829",
"components": [ ... ],
"waba_connection_id": "3b9e2c1d-…",
"created_at": "2026-09-10T12:00:00.000Z",
"last_synced_at": "2026-09-10T12:00:03.000Z"
}
],
"meta": { "page": 1, "per_page": 50, "total_pages": 1, "total_count": 12 }
}| Filtro | Como funciona |
|---|---|
status | Status da Meta, sem diferenciar maiúsculas: APPROVED, PENDING, REJECTED, PAUSED… Valor desconhecido devolve lista vazia. |
category | MARKETING, UTILITY ou AUTHENTICATION, sem diferenciar maiúsculas. |
waba_connection_id | UUID da conexão; de outra conta, lista vazia. |
phone_number_id | O id do Número na Meta: lista os templates da conexão dele. Número que não é da conta, lista vazia. |
Uma chave sandbox lista só o catálogo sintético do sandbox; uma de produção, só o de produção.
rejection_reason traz o motivo que a Meta informou para um template REJECTED (ex.: INVALID_FORMAT, INCORRECT_CATEGORY, TAG_CONTENT_MISMATCH) e é null nos demais status ou quando a Meta não mandou motivo. A tela de Templates do painel mostra o mesmo motivo, em português.
Consultar um template
GET /api/v1/templates/{id} recebe o UUID interno (o id da lista, não o meta_template_id) e devolve o mesmo objeto. Template de outra conta, de outro ambiente ou inexistente é 404 not_found. Vale o mesmo recorte da lista: uma chave sandbox consulta só o catálogo sintético do sandbox, e uma de produção nunca o vê.
Criar um template
POST /api/v1/templates (escopo templates:write) valida o template e o submete à análise da Meta. Informe a conexão por waba_connection_id ou pelo phone_number_id da Meta. O corpo usa o formato de criação da Meta (tipos em maiúsculas: HEADER, BODY, FOOTER, BUTTONS), diferente do formato de envio:
POST /api/v1/templates
{
"phone_number_id": "106540352242922",
"name": "pedido_enviado",
"language": "pt_BR",
"category": "UTILITY",
"components": [
{
"type": "HEADER",
"format": "IMAGE",
"example": { "header_handle": ["4::aW1hZ2UvanBlZw==:ARb…"] }
},
{
"type": "BODY",
"text": "Oi {{1}}, seu pedido {{2}} saiu para entrega.",
"example": { "body_text": [["Maria", "#4821"]] }
},
{ "type": "FOOTER", "text": "Loja Exemplo" },
{
"type": "BUTTONS",
"buttons": [
{
"type": "URL",
"text": "Rastrear",
"url": "https://loja.exemplo.com/rastreio/{{1}}",
"example": ["https://loja.exemplo.com/rastreio/4821"]
}
]
}
]
}A BotoZap confere antes de chamar a Meta:
name— letras minúsculas, números e_, até 512 caracteres (é convertido para minúsculas);language— no formatopt_BR,en_US,es;category—MARKETING,UTILITYouAUTHENTICATION. A Meta pode reclassificar: a resposta traz a categoria que ela devolveu;BODYobrigatório, até 1024 caracteres, com variáveis sequenciais ({{1}},{{2}}…) e um exemplo para cada uma emexample.body_text;HEADERde texto até 60 caracteres e no máximo uma variável ({{1}}, com exemplo emexample.header_text); cabeçalho de imagem, vídeo ou documento exigeexample.header_handle— ohandlede POST /api/v1/media com delivery=meta_resumable_asset;FOOTERaté 60 caracteres, sem variável;BUTTONScom 1 a 3 botões, rótulo até 25 caracteres:QUICK_REPLY,URL(com exemplo quando a URL tem variável),PHONE_NUMBER,COPY_CODE(exemplo até 15 caracteres) eCATALOG. BotõesFLOWnão são aceitos;AUTHENTICATIONtem formato próprio de OTP (e é a única categoria com botãoOTP).
O que falha aqui é 422 invalid_template, com a mensagem dizendo o quê. Campos fora do formato são descartados antes de ir à Meta.
Criar um template
Submete um template à Meta. Informe a WABA com `waba_connection_id` ou `phone_number_id` — os dois vêm de `GET /api/v1/phone_numbers`.
curl https://botozap.com.br/api/v1/templates \
-X POST \
-H "X-API-Key: $BOTOZAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "pedido_confirmado",
"language": "pt_BR",
"category": "UTILITY",
"phone_number_id": "109876543210987",
"components": [
{
"type": "BODY",
"text": "Olá {{1}}, seu pedido foi confirmado.",
"example": {
"body_text": [
[
"Maria"
]
]
}
}
]
}'Resposta 201
{
"data": {
"id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
"name": "pedido_confirmado",
"language": "pt_BR",
"category": "UTILITY",
"status": "PENDING",
"rejection_reason": null,
"meta_template_id": "123456789012345",
"components": [
{
"type": "BODY",
"text": "Olá {{1}}, seu pedido foi confirmado.",
"example": {
"body_text": [
[
"Maria"
]
]
}
}
],
"waba_connection_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
"created_at": "2026-07-14T12:00:00.000Z",
"last_synced_at": "2026-07-14T12:00:00.000Z"
}
}Sem `waba_connection_id` e sem `phone_number_id`: 422 `missing_connection`. Conexão ou número que não é da conta: 404 `not_found`.
`name` só aceita minúsculas, números e underscore (`pedido_confirmado`). `language` no formato `pt_BR`. `category`: `UTILITY`, `MARKETING` ou `AUTHENTICATION`.
O corpo é obrigatório. Variáveis precisam ser sequenciais (`{{1}}`, `{{2}}`) e cada uma leva um valor em `example.body_text`. Payload local inválido é 422 `invalid_template` — a Meta não é chamada.
Em live o 201 volta com `status: PENDING`; só envie o template quando a listagem mostrar `APPROVED`. A Graph recusando o conteúdo é 422 `meta_error`; 5xx da Graph é 502 `meta_error`.
Chave sandbox cria o template já `APPROVED`, sem chamar a Meta. Use a conexão/número sandbox da conta — misturar live e sandbox é 404.
Nome e idioma que já existem no catálogo da conexão: 409 `duplicate`, em live e no sandbox, antes de chamar a Meta. O template existente não é alterado.
Erros ao criar
| HTTP | code | Quando |
|---|---|---|
| 422 | missing_connection | Sem waba_connection_id nem phone_number_id. |
| 404 | not_found | Conexão ou número que não é da conta, conexão inativa ou de outro ambiente (sandbox × produção); no sandbox, também quando o ambiente sintético ainda não foi criado. |
| 422 | invalid_template | Falhou numa das regras acima. |
| 503 | plan_unavailable | Produção: não foi possível ler o plano da conta. Nada foi enviado à Meta; repita com backoff. |
| 409 | not_connected | A conexão não tem token de acesso: refaça a conexão. |
| 422 / 502 | meta_error | A Meta recusou (422) ou falhou (502). Um nome que já existe na WABA mas ainda não foi importado para o catálogo chega aqui como recusa da Meta: sincronize os templates no painel e ele passa a responder duplicate. |
| 502 | create_failed | Falha inesperada na chamada à Meta. |
| 409 | duplicate | Nome e idioma já existem no catálogo da conexão (sandbox ou produção). Nada é alterado e a Meta não é chamada. |
| 502 | persist_failed | A Meta recebeu o template, mas o catálogo não gravou. Não crie de novo (a Meta recusaria o nome repetido): sincronize os templates no painel. |
Sem conexão informada, a API responde antes de olhar o resto do corpo:
Erro: criar template sem conexão
O POST de template não adivinha a WABA. Sem `waba_connection_id` e sem `phone_number_id`, a API recusa antes de validar o restante do payload.
curl https://botozap.com.br/api/v1/templates \
-X POST \
-H "X-API-Key: $BOTOZAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "pedido_confirmado",
"language": "pt_BR",
"category": "UTILITY",
"components": [
{
"type": "BODY",
"text": "Olá, seu pedido foi confirmado."
}
]
}'Resposta 422
{
"error": {
"code": "missing_connection",
"message": "Informe 'waba_connection_id' ou 'phone_number_id' para criar o template."
}
}Pegue os dois ids em `GET /api/v1/phone_numbers`: `waba_connection_id` é o UUID da conexão; `phone_number_id` é o id Meta do número. Basta um dos dois.
Aprovação: de PENDING a APPROVED
A resposta do POST traz o status que a Meta devolveu na criação — em geral PENDING. A análise da Meta leva de minutos a horas e termina em APPROVED ou REJECTED; depois, a Meta ainda pode mudar o status (ex.: PAUSED por qualidade).
A BotoZap recebe essas mudanças da Meta e atualiza o status do catálogo, mas não dispara um evento para o seu webhook. Para saber quando o template ficou pronto, consulte GET /api/v1/templates/{id} (ou a lista com status=pending) de tempos em tempos. Numa rejeição, rejection_reason traz o motivo informado pela Meta. Enviar um template ainda PENDING é recusado pela Meta (422 meta_error).
Sandbox
Com chave bz_sandbox_, POST /api/v1/templates grava no catálogo sintético do sandbox sem chamar a Meta, e o template já nasce APPROVED (com last_synced_at: null). Criar de novo o mesmo nome e idioma responde 409 duplicate e mantém o existente, como em produção. O envio pelo sandbox aceita qualquer nome, esteja ou não no catálogo — o catálogo serve para você testar a listagem e o seu fluxo de criação.
O que só o painel faz
- Editar um template (a edição volta para análise da Meta; nome e idioma não mudam);
- Excluir um template;
- Sincronizar o catálogo de um Cliente com a Meta — é o que importa templates criados direto no WhatsApp Manager, remove os que foram excluídos lá, grava o
rejection_reasone corrige umpersist_failed. Até lá, essas mudanças não aparecem emGET /api/v1/templates.
No painel, fica em Campanhas › Templates.
