Como começar
1. Gere uma chave. No painel, entre em Integrações e clique em gerar chave. Dê um nome que diga de onde ela vem ("Site", "Sistema da clínica", "n8n"). A chave aparece uma única vez — copie na hora. Guardamos apenas o hash dela, então não há como recuperá-la depois; se perder, gere outra e revogue a antiga.
2. Anote o seu endereço base. É o mesmo domínio onde você acessa o painel, com /api/v1 no fim:
https://SEU-DOMINIO/api/v1
3. Confirme que está tudo certo:
curl https://SEU-DOMINIO/api/v1/ping \
-H "X-API-Key: atz_sua_chave_aqui"
{ "ok": true, "ts": "2026-09-28T14:03:11.204Z" }
Todas as respostas são JSON. As listas vêm sempre dentro de data.
Os horários são ISO 8601 em UTC.
Autenticação
Toda requisição precisa da chave, em um dos dois headers — tanto faz qual:
X-API-Key: atz_sua_chave_aqui
Authorization: Bearer atz_sua_chave_aqui
A chave age com as permissões do dono da conta: ela enxerga e faz tudo o que o dono faria. Trate-a como senha — não coloque em código que vai para o navegador nem em repositório público.
Cada chave registra a data do último uso, que aparece no painel. É a forma mais simples de
descobrir se uma integração parou: se a data congelou, parou. Revogar é imediato — quem estiver usando
aquela chave recebe 401 na requisição seguinte.
Limites e erros
| Situação | Limite |
|---|---|
| Com chave válida | 300 requisições por minuto |
| Sem chave | 30 por minuto, por IP |
A resposta traz os headers padrão RateLimit-* com o que resta da janela. Estourando, vem
429 e basta esperar um minuto.
| Código | O que significa |
|---|---|
200 / 201 | Deu certo (201 quando algo foi criado). |
400 | Falta um campo ou o valor não serve. A mensagem diz qual. |
401 | Chave ausente, inválida ou revogada. |
404 | O que você pediu não existe nesta conta. |
409 | Existe, mas não está em estado de ser usado (ex.: modelo ainda não aprovado). |
429 | Limite de requisições. |
502 | Nós aceitamos, mas o WhatsApp recusou o envio. A mensagem traz o motivo. |
Erros vêm sempre no mesmo formato:
{ "error": "Telefone inválido." }
Contatos
Não faz nada e não altera nada — serve para confirmar que a chave é válida e que o endereço está certo. É por aqui que se começa a depurar quando algo não responde.
{ "ok": true, "ts": "2026-09-28T14:03:11.204Z" }
| Parâmetro | Descrição |
|---|---|
search | Procura por nome ou telefone. |
limit | Padrão 50, máximo 200. |
{ "data": [
{ "id": 412, "name": "Maria Souza", "phone": "5521999998888",
"email": null, "birthday": null, "created_at": "2026-09-12 14:02:55" }
] }
| Campo | Descrição |
|---|---|
phone obrigatório | Só dígitos, com DDI e DDD. Pontuação é ignorada. |
name | Se vazio, usamos o próprio telefone como nome. |
email | Opcional. |
Se o telefone já existir, não duplicamos: atualizamos nome e e-mail (quando vierem preenchidos)
e devolvemos "created": false. Pode chamar à vontade, é seguro repetir.
Agendamentos
| Parâmetro | Descrição |
|---|---|
from | Data/hora inicial (ISO). Sem ela, começa em ontem. |
to | Data/hora final (ISO). |
limit | Padrão 100, máximo 500. |
Devolve só o que está de pé — scheduled, confirmed ou done — em ordem de horário.
Cancelados ficam de fora.
{ "data": [
{ "id": 903, "title": "Consulta", "starts_at": "2026-09-30T13:00:00.000Z",
"duration_min": 30, "status": "scheduled", "resource_id": null,
"contact_name": "Maria Souza", "contact_phone": "5521999998888" }
] }
| Campo | Descrição |
|---|---|
contact_phone ou contact_id obrigatório |
Pelo telefone é o mais simples: se o contato não existir, criamos. |
title obrigatório | O que vai aparecer na agenda ("Consulta", "Retorno"). |
starts_at obrigatório | Início, em ISO 8601 com fuso. |
duration_min | Padrão 30. |
curl -X POST https://SEU-DOMINIO/api/v1/appointments \
-H "X-API-Key: atz_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"contact_phone": "5521999998888",
"title": "Consulta",
"starts_at": "2026-09-30T10:00:00-03:00",
"duration_min": 30
}'
O agendamento nasce como scheduled, aparece na agenda do painel na hora, e dispara o
webhook appointment.created com "source": "api".
Atenção a quem usa etiquetas nos relatórios. Um agendamento criado por aqui entra sem etiquetas. Se a sua operação conta leads ou separa profissionais por etiqueta, decida antes quem vai colocá-las — senão o agendamento existe na agenda e some dos relatórios.
Conversas
| Parâmetro | Descrição |
|---|---|
status | open, waiting ou closed. |
limit | Padrão 50, máximo 200. |
{ "data": [
{ "id": 1841, "status": "open", "updated_at": "2026-09-28 11:27:04",
"contact_name": "Maria Souza", "contact_phone": "5521999998888",
"attendant_name": "Raphaella" }
] }
Enviar mensagem
| Campo | Descrição |
|---|---|
phone obrigatório | Destino, só dígitos com DDI e DDD. |
body | O texto. Obrigatório, exceto se usar template_name. |
line_id | Por qual número enviar. Sem ele, reaproveitamos a conversa ativa do contato
(e a linha dela) ou usamos a linha padrão. Um line_id que não existe dá 400 —
nunca cai na padrão em silêncio. |
name | Nome do contato vindo do seu sistema. Prevalece sobre o nome do perfil do WhatsApp — quem dispara sabe de quem se trata. |
tags | Até 5 etiquetas, criadas se não existirem: ["Vaga: Repositor"]. |
assign_to | Id ou e-mail de quem fica com a conversa. Se não corresponder a ninguém ativo,
enviamos mesmo assim, com atribuição automática, e devolvemos um aviso. |
pause_ai | Pausa a assistente nesta conversa, para ela não responder por cima de quem
está assumindo. Aceita true, 1 ou "1". |
curl -X POST https://SEU-DOMINIO/api/v1/messages \
-H "X-API-Key: atz_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"phone": "5521999998888",
"body": "Olá, Maria! Sua consulta é amanhã às 10h.",
"name": "Maria Souza"
}'
{ "data": {
"message_id": 58120, "wa_message_id": "3EB0...", "conversation_id": 1841,
"line_id": 2, "template": null, "assigned_to": 7
} }
Fora da janela de 24 horas (linhas na API oficial)
Numa linha da API oficial do WhatsApp, a Meta só aceita mensagem iniciada pela empresa se ela usar um
modelo aprovado. Nesse caso, em vez de body:
| Campo | Descrição |
|---|---|
template_name | Nome do modelo. Exige line_id. |
template_language | Padrão pt_BR. |
template_params | As variáveis, na ordem. Nenhuma pode ser vazia nem conter quebra de linha. |
Os modelos disponíveis estão em GET /v1/templates. As recusas são específicas:
404 se o modelo não existe naquela linha, 409 se ainda não foi aprovado pela Meta,
400 se o número de variáveis não bate.
Mensagens enviadas
O que nós enviamos, com o estado de entrega — serve para o seu sistema mostrar se a pessoa recebeu, sem ninguém precisar abrir o atendimento. Não inclui o que o cliente escreveu.
| Parâmetro | Descrição |
|---|---|
line_id | Limita a um número. |
days | Janela em dias. Padrão 7, máximo 90. |
limit | Padrão 300, máximo 1000. |
A resposta traz total além de data: se total for maior que o número de itens,
o limite cortou e há mais para buscar.
{ "data": [
{ "id": 58120, "timestamp": "2026-09-28 11:02:00", "body": "Olá, Maria!...",
"wa_status": 4, "failed": 0, "wa_message_id": "3EB0...",
"conversation_id": 1841, "contact_name": "Maria Souza",
"phone": "5521999998888", "sender_name": "Raphaella" }
], "total": 412, "limit": 300 }
wa_status é um número, e o que interessa é o degrau:
| Valor | Significa |
|---|---|
0 / 1 | Ainda saindo. |
2 | Chegou ao servidor do WhatsApp. |
3 | Entregue no aparelho. |
4 / 5 | Lida (5 = áudio ouvido). |
Compare por >=, não por igualdade: wa_status >= 3 é "chegou",
>= 4 é "leu". failed: 1 marca o que não saiu.
Usuários
Quem pode receber uma conversa — para o seu sistema oferecer uma lista em vez de adivinhar ids.
Com ?line_id=, quem é do setor daquela linha vem primeiro (no_setor: 0).
{ "data": [
{ "id": 7, "name": "Raphaella", "email": "...", "role": "attendant", "no_setor": 0 }
], "department_id": 3 }
Modelos aprovados
line_id é obrigatório. Mostra o texto exatamente como o cliente recebe, com o estado
na Meta e quantas variáveis cada um espera.
{ "data": [
{ "name": "lembrete_consulta", "language": "pt_BR", "status": "APPROVED",
"body": "Olá {{1}}, sua consulta é {{2}}.", "vars": 2 }
] }
Custo da Meta
Somente leitura: o que a Meta cobra naquela linha, por categoria (UTILITY, AUTHENTICATION, MARKETING,
SERVICE). Exige line_id de uma linha na API oficial, com conta da Meta configurada.
?days= define a janela (sem ele, o mês corrente); ?categorias= filtra.
Webhooks de saída
O caminho inverso: nós avisamos o seu sistema quando algo acontece. Configure em
Integrações → Webhooks — URL, segredo e quais eventos. Há um botão de teste que dispara um
evento test na hora.
| Evento | Quando |
|---|---|
message.received | O cliente enviou uma mensagem. |
appointment.created | Nasceu um agendamento (inclusive os criados pela API). |
pix.paid | Um pagamento Pix foi confirmado. |
Fazemos POST com este corpo:
{
"event": "message.received",
"data": {
"conversation_id": 1841,
"contact": { "name": "Maria Souza", "phone": "5521999998888" },
"body": "Bom dia, queria remarcar",
"message_id": 58121,
"line_id": 2
},
"timestamp": "2026-09-28T14:03:11.204Z"
}
Confirme que veio mesmo de nós
Cada envio leva a assinatura do corpo em HMAC-SHA256, com o segredo que você configurou:
X-Atendize-Event: message.received
X-Atendize-Signature: sha256=3f5a...
User-Agent: Atendize-Webhooks/1.0
// Node.js — valide sobre o corpo CRU, antes de qualquer parse
const esperado = 'sha256=' + require('crypto')
.createHmac('sha256', SEGREDO).update(corpoCru).digest('hex');
if (esperado !== req.headers['x-atendize-signature']) return res.sendStatus(401);
Responda rápido, com 2xx. Desistimos em 8 segundos e tentamos
até 3 vezes, com intervalo crescente. Se o seu endpoint demora, responda primeiro e processe depois —
senão uma tarefa lenta vira reentrega e você processa o mesmo evento duas vezes.
Como pode haver reentrega, trate os eventos como repetíveis:
use message_id (ou id) para ignorar o que já processou.
Feed de calendário
Antes de escrever qualquer código: se tudo o que você precisa é ver a agenda num calendário, não precisa da API. Há um feed iCal que o Google Agenda, o Outlook e o Apple Calendar assinam direto.
O token é gerado no painel, em Integrações → Feed de calendário. O endereço fica assim:
https://SEU-DOMINIO/contacts/calendar/feed.ics?token=SEU_TOKEN
É só colar em "Assinar por URL" (ou "Adicionar por URL") no aplicativo de calendário. Cada compromisso vira um evento com o serviço e o nome no título, e telefone, profissional e estado na descrição. Entram os agendamentos dos últimos 60 dias em diante, até 1000, já marcados ou concluídos.
Três limites que valem saber antes de escolher este caminho. É somente leitura — nada volta do calendário para cá. A atualização é periódica, não instantânea: o Google costuma reler de poucas em poucas horas, e isso não se controla do nosso lado. E o token vai no endereço, então quem tiver o link vê a agenda — se vazar, gere outro no painel, que o antigo morre na hora.
Para agenda em tempo real ou nos dois sentidos, aí sim é a API de agendamentos.