API Documentation
Integre a plataforma Agilizai ao seu backend. Crie campanhas, gerencie webhooks, consulte limites operacionais e controle conexões de canal via API.
Primeiros Passos
Siga estes 6 passos para começar a integrar com a API Agilizai em poucos minutos.
Passo 1.Gere sua API Key
Acesse Configurações > API Keys no dashboard, crie uma nova chave e selecione os escopos necessários para sua integração.
Abrir API KeysPasso 2.Teste a conexão
Faça uma requisição GET para /v1/limits com sua chave para validar autenticação e permissões.
Ver exemplo de requestPasso 3.Conecte seu WhatsApp
Crie uma instância via POST /v1/whatsapp/instances, escaneie o QR code com GET .../qr e aguarde a conexão via polling em GET .../status.
Ver fluxo de conexãoPasso 4.Valide modalidade e limites
Confirme se a empresa está habilitada (mensal ou avulso) e verifique limite diário, concorrência e RPM no endpoint /v1/limits.
Ir para limitesPasso 5.Crie uma campanha
Use o endpoint POST /v1/campaigns para criar sua primeira campanha de envio em massa com sua lista de contatos.
Ver payload completoPasso 6.Acompanhe o status
Monitore o andamento via GET /v1/campaigns/:id e /v1/campaigns/:id/messages ou configure webhooks para eventos em tempo real.
Configurar webhooksAutenticação
Todas as requisições devem incluir sua API key no header Authorization.
GET /v1/limits HTTP/1.1
Host: api-worker.agilizai.tech
Authorization: Bearer ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/jsonTipos de API Key
ak_live_xxxxxxxx...- Envia mensagens reais via WhatsApp
- Usa os limites e regras reais da empresa
- Dispara webhooks normalmente
- Use em ambientes de producao
ak_test_xxxxxxxx...- Não envia mensagens reais ao WhatsApp
- Simula envio com 95% sucesso e 5% falha
- Não consome creditos da empresa
- Use para testar integracoes com segurança
Ciclo de vida da API Key
Criar: Acesse Configurações > API Keys no dashboard. Selecione o nome e os scopes necessários. A key completa e exibida apenas uma vez. Copie e armazene em local seguro.
Rotacionar: Gere uma nova key sem invalidar a antiga imediatamente. Atualize seus sistemas e depois revogue a key antiga.
Revogar: Invalide uma key imediatamente. Requisicoes com essa key retornarao 401 Unauthorized.
Scopes e Permissoes
Cada API key possui scopes que definem quais endpoints podem ser acessados. Selecione apenas os necessários (principio do menor privilegio).
| Scope | Descrição | Endpoints |
|---|---|---|
messages:write | Enviar mensagens individuais | POST /v1/messages |
campaigns:read | Ler campanhas e status de mensagens | GET /v1/campaigns, GET /v1/campaigns/:id, GET /v1/campaigns/:id/messages |
campaigns:write | Criar, pausar, retomar e cancelar campanhas | POST /v1/campaigns, POST /v1/campaigns/:id/cancel, POST /v1/campaigns/:id/pause, POST /v1/campaigns/:id/resume |
webhooks:manage | Gerenciar webhooks | POST/GET/PUT/DELETE /v1/webhooks, POST /v1/webhooks/:id/test |
whatsapp:manage | Gerenciar instancias WhatsApp | POST/GET /v1/whatsapp/instances, GET /v1/whatsapp/instances/:id/qr, GET /v1/whatsapp/instances/:id/status, POST /v1/whatsapp/instances/:id/disconnect |
credits:read | Consultar limites, capacidade operacional e saldos/recarga | GET /v1/limits, GET /v1/billing/balance |
imports:write | Enfileirar importacao de contatos (uso interno) | POST /v1/imports |
tickets:read | Ler tickets, detalhe e historico de mensagens; discovery de times, atendentes, fila, caixas e respostas rapidas | GET /v1/tickets, GET /v1/tickets/:id, GET /v1/tickets/:id/messages, GET /v1/teams, GET /v1/agents, GET /v1/queue, GET /v1/setores, GET /v1/quick-replies |
tickets:write | Abrir/fechar, atribuir, etiquetar, vincular e transbordar tickets | POST /v1/tickets, PATCH /v1/tickets/:id/status|assignee|priority, POST/DELETE /v1/tickets/:id/tags, POST /v1/tickets/:id/links, POST /v1/tickets/:id/handoff |
contacts:read | Listar/buscar contatos e ler custom fields | GET /v1/contacts, GET /v1/contacts/:id, GET /v1/contact-fields |
contacts:write | Criar/atualizar contato, custom_fields e pausar/retomar a IA | POST /v1/contacts, PATCH /v1/contacts/:id, PATCH /v1/contacts/:id/assistant |
appointments:read | Tipos, disponibilidade, listagem e detalhe de agendamentos + discovery de profissionais e unidades | GET /v1/appointment-types, GET /v1/appointments/availability, GET /v1/appointments, GET /v1/appointments/:id, GET /v1/professionals, GET /v1/units |
appointments:write | Criar, reagendar, cancelar e marcar comparecimento | POST /v1/appointments, PATCH /v1/appointments/:id, DELETE /v1/appointments/:id |
crm:read | Listar etapas, oportunidades (com contagem e detalhe) e catalogo de produtos do funil | GET /v1/crm/stages, GET /v1/crm/opportunities, GET /v1/crm/opportunities/count, GET /v1/crm/opportunities/:id, GET /v1/crm/products |
crm:write | Criar, editar, mover etapa e remover oportunidades; criar produtos | POST /v1/crm/opportunities, PATCH /v1/crm/opportunities/:id, POST /v1/crm/opportunities/:id/move-stage, DELETE /v1/crm/opportunities/:id, POST /v1/crm/products |
scheduled_messages:read | Listar e consultar mensagens agendadas | GET /v1/scheduled-messages, GET /v1/scheduled-messages/:id |
scheduled_messages:write | Agendar e cancelar mensagens 1:1 | POST /v1/scheduled-messages, DELETE /v1/scheduled-messages/:id |
tags:read | Listar tags e regras de auto-tag | GET /v1/tags, GET /v1/tag-rules |
tags:write | Criar, editar e remover tags e regras de auto-tag | POST /v1/tags, PATCH/DELETE /v1/tags/:id, POST /v1/tag-rules, PATCH/DELETE /v1/tag-rules/:id |
contracts:read | Listar contratos e modelos | GET /v1/contracts, GET /v1/contracts/:id, GET /v1/contract-templates |
contracts:write | Criar rascunho, editar, cancelar e enviar contratos para assinatura | POST /v1/contracts, PATCH/DELETE /v1/contracts/:id, POST /v1/contracts/:id/send |
leads:read | Consultar CNAEs e municípios para montar buscas de leads | GET /v1/leads/cnaes, GET /v1/leads/municipios |
leads:write | Buscar empresas, validar WhatsApp/e-mail e salvar leads no CRM | POST /v1/leads/search, POST /v1/leads/save-to-crm, POST /v1/leads/validate-whatsapp, POST /v1/leads/validate-email-mx |
reports:read | Métricas do período (mensagens, tempo de resposta, campanhas) e avaliações | GET /v1/reports/summary, GET /v1/ratings |
setor_id— useGET /v1/setorespara listar as caixas/números da empresa com seus IDs.client_id— e retornado ao criar contatos ou pode ser consultado no painel de Contatos do dashboard.
Segurança da API Key
- Nunca exponha sua key em código frontend, repositorios públicos ou logs.
- Use variáveis de ambiente para armazenar keys no servidor.
- Rotacione periodicamente: recomendamos a cada 90 dias.
- Revogue imediatamente se suspeitar de vazamento.
- Restrinja scopes: uma key para criar campanhas não precisa de
whatsapp:manage.
Endpoints
Referência completa dos endpoints disponíveis, organizados por categoria.
Referência interativa completa
Todos os endpoints — clique em qualquer um e veja entrada (params/body), saída (response) e exemplo pronto em várias linguagens.
Envio de Mensagem Única
O endpoint POST /v1/messages permite enviar uma única mensagem WhatsApp de forma sincrona. Ideal para boas-vindas, notificações de status, lembretes de agendamento e qualquer comúnicacao 1-para-1 disparada por evento.
Casos de uso tipicos
- Mensagem de boas-vindas após cadastro
- Lembrete de agendamento ou consulta
- Notificacao de status (pedido enviado, pagamento confirmado)
- Confirmação de código ou link de acesso
Diferenca de Campanhas em Massa
- Síncrono - a resposta já confirma se a mensagem foi enviada
- Sem delays anti-ban - envio imediato, sem fila de campanha
- Sem tracking de campanha - não cria campanha nem gera relatórios de lote
- Para envios em massa (>1 destinatario), use
POST /v1/campaigns
Request
/v1/messagesScope necessário: messages:write
Headers
| Header | Valor |
|---|---|
| Authorization | Bearer YOUR_API_KEY |
| Content-Type | application/json |
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| phone | string | Obrigatório* | Numero do destinatario com DDI (ex: +5511999999999). Use phone ou client_id. Se ambos forem enviados, client_id tem prioridade. |
| client_id | string | Obrigatório* | ID de um contato existente na base. Use phone ou client_id. |
| setor_id | string | Não | Opcional. Se omitido, o sistema seleciona automaticamente um número WhatsApp conectado da sua empresa. |
| multi_number | boolean | Não | Opcional. Se true, faz o rodízio automatico entre todos os números elegíveis da empresa (mutuamente exclusivo com setor_id, setor_ids e team_id). |
| setor_ids | string[] | Não | Opcional. Lista de UUIDs de setores para rodízio entre números específicos. |
| team_id | string | Não | Opcional. UUID de um time — rodízio entre as caixas desse time. Um modo de pool por chamada (multi_number, setor_ids ou team_id). |
| message | object | Sim | Conteudo da mensagem (text, media ou template) |
| message.text | string | Condicional | Texto da mensagem (obrigatório se não enviar media/template) |
| message.media_url | string | Não | URL pública da mídia (HTTPS) |
| message.media_type | string | Condicional | Tipo da mídia: image, video, audio, document. Obrigatório se media_url informado. |
| message.media_delivery | string | Não | Ordem da mídia com o texto: inline (legenda junto — padrão para imagem, video e document), after_text (texto e depois a mídia) ou media_first (mídia antes do texto). Áudio nunca usa legenda. |
| message.media_filename | string | Não | Nome do arquivo exibido ao destinatario (útil para document). |
| message.template_name | string | Nao** | Nome do template aprovado no Meta Business (para Meta Cloud API) |
| message.template_language | string | Nao** | Código do idioma (ex: pt_BR) |
| message.template_components | array | Nao | Componentes do template Meta (header, body, buttons) conforme especificacao da Cloud API |
* Informe phone ou client_id — pelo menos um e obrigatório.
** Obrigatório apenas para envio de templates Meta. Para mensagens de texto ou mídia, esses campos devem ser omitidos.
Exemplo - Mensagem de Texto
{
"phone": "+5511999999999",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"message": {
"text": "Ola Maria! Seu agendamento para amanha as 14h esta confirmado."
}
}Exemplo - Mensagem com Midia
{
"phone": "+5511999999999",
"message": {
"media_url": "https://cdn.example.com/comprovante.pdf",
"media_type": "document"
}
}Exemplo - Template (Meta Cloud API)
{
"phone": "+5511999999999",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"message": {
"template_name": "appointment_reminder",
"template_language": "pt_BR",
"template_components": [
{ "type": "body", "parameters": [{ "type": "text", "text": "Maria" }, { "type": "text", "text": "15/03/2026" }, { "type": "text", "text": "14:00" }] }
]
}
}Response - 201 Created
{
"success": true,
"data": {
"message_id": "6f6e5d4c-3b2a-4190-8877-665544332211",
"phone": "+5511999999999",
"status": "sent",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"sender_number": "+5551999001234",
"timestamp": "2026-03-15T14:30:00.000Z"
}
}Erros
| Status | Código | Descrição |
|---|---|---|
| 400 | BAD_REQUEST | Corpo inválido - campo obrigatório ausente, formato incorreto ou telefone inválido (INVALID_NUMBER) |
| 401 | UNAUTHORIZED | API key ausente ou invalida |
| 402 | BULK_DISABLED / PLAN_EXPIRED | Saldo de envios esgotado (recarregue creditos) ou conta em modo somente leitura/suspensa |
| 403 | FORBIDDEN / MULTI_NUMBER_DISABLED | Scope messages:write ausente, ou envio por vários números pedido sem o recurso multi-número habilitado |
| 404 | NOT_FOUND | Setor não encontrado ou sem instancia WhatsApp conectada |
| 500 | INTERNAL_ERROR | Erro interno ao tentar enviar a mensagem |
| 502 | PROVIDER_ERROR | Falha na entrega pelo provedor de WhatsApp (o crédito é estornado automaticamente) |
cURL
curl -X POST https://api-worker.agilizai.tech/v1/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999999999",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"message": {
"text": "Ola Maria! Seu agendamento para amanha as 14h esta confirmado."
}
}'+5511987654321, 5511987654321, +55 (11) 98765-4321.Para envios em massa com mais de 1 destinatario, use POST /v1/campaigns que inclui delays anti-ban, agendamento e acompanhamento por dashboard/webhooks.
Primeiro Contato
O endpoint POST /v1/messages/first-contact e ideal para integracoes com CRMs, formularios e landing pages. Ele envia a mensagem, cria o contato automaticamente se não existir, estabelece afinidade com o numero remetente e permite aplicar tags ao contato.
Casos de uso
- Lead capturado em formulario do site
- Novo cadastro em landing page de campanha
- Integração com CRM após qualificação de lead
- Boas-vindas automáticas após opt-in
Request
/v1/messages/first-contactScope necessário: messages:write
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| phone | string | Sim | Numero do destinatario com DDI (ex: +5511999999999) |
| name | string | Nao | Nome do contato. Usado na criação automatica do registro |
| message | string | Condicional | Texto da mensagem. Obrigatório se media_url não informado |
| media_url | string | Condicional | URL pública da mídia (HTTPS). Obrigatório se message omitido |
| media_type | string | Condicional | Tipo da mídia: image, video, audio, document. Obrigatório com media_url |
| media_delivery | string | Nao | Ordem da mídia com o texto: inline (padrão), after_text ou media_first. |
| media_filename | string | Nao | Nome do arquivo exibido ao destinatario (útil para document). |
| setor_id | string | Nao | Se omitido, usa afinidade existente ou auto-seleciona |
| multi_number | boolean | Nao | Rodízio automatico entre os números elegíveis (exclusivo com setor_id/setor_ids/ team_id). |
| setor_ids | string[] | Nao | Lista de UUIDs de setores para rodízio entre números específicos. |
| team_id | string | Nao | UUID de um time — rodízio entre as caixas do time. |
| tags | string[] | Nao | IDs de tags para aplicar ao contato automaticamente |
| template_name | string | Condicional* | Nome do template Meta aprovado. Obrigatório no primeiro contato por número oficial (Meta) — texto/mídia livre não é entregue. |
| template_language | string | Condicional | Código do idioma (ex: pt_BR) |
| template_components | array | Nao | Componentes do template Meta (header, body, buttons) conforme a Cloud API. |
* Em números oficiais (Meta), o primeiro contato abre uma conversa nova (janela de 24h fechada): só um template aprovado é entregue. Envie template_name (texto/mídia livre retorna 400). Números não-oficiais aceitam texto e mídia normalmente.
Exemplo
{
"phone": "+5511999990001",
"name": "Maria Silva",
"message": "Ola Maria! Vi que você se cadastrou no nosso site. Como posso ajudar?",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"tags": ["c1d2e3f4-a5b6-4c78-9d0e-1f2a3b4c5d6e", "d2e3f4a5-b6c7-4d89-ae0f-2a3b4c5d6e7f"]
}Response - 201 Created
{
"success": true,
"data": {
"message_id": "6f6e5d4c-3b2a-4190-8877-665544332211",
"client_id": "9f8e7d6c-5b4a-4392-a01f-2e3d4c5b6a79",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"sender_number": "+5551999001234",
"phone": "+5511999990001",
"status": "sent",
"affinity_created": true,
"affinity_source": "first_contact",
"timestamp": "2026-03-15T14:30:00.000Z"
}
}Diferenca do POST /v1/messages
| Aspecto | /v1/messages | /v1/messages/first-contact |
|---|---|---|
| Criacao de contato | Sim (se phone novo) | Sim (sempre, com name opcional) |
| Afinidade | Upsert silencioso | Upsert com source tracking |
| Tags | Não suporta | Suporta array de tag IDs |
| Input por client_id | Sim | Não (sempre por phone) |
| Resposta | message_id, status | + client_id, affinity_created |
Mensagens Interativas
O endpoint POST /v1/messages/interactive permite enviar mensagens com botões clicáveis ou listas de opções nativas do WhatsApp. Ideal para qualificação de leads, pesquisas rapidas e menus de atendimento.
Request
/v1/messages/interactiveScope necessário: messages:write
Botoes (button)
Até 3 botões clicáveis. Título máximo de 20 caracteres por botão.
Lista (list)
Até 10 opções em formato lista. Título máximo de 24 caracteres por opção.
| Tipo | Max itens | Max caracteres título |
|---|---|---|
| button | 3 botões | 20 |
| list | 10 rows | 24 |
Exemplo - Botoes
{
"phone": "+5511999990001",
"interactive": {
"type": "button",
"body": { "text": "Qual horário prefere para a visita?" },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "btn_manha", "title": "Manha (9h-12h)" } },
{ "type": "reply", "reply": { "id": "btn_tarde", "title": "Tarde (14h-18h)" } },
{ "type": "reply", "reply": { "id": "btn_noite", "title": "Noite (19h-21h)" } }
]
}
}
}Exemplo - Lista
{
"phone": "+5511999990001",
"interactive": {
"type": "list",
"body": { "text": "Selecione o servico desejado:" },
"action": {
"sections": [
{
"rows": [
{ "id": "srv_1", "title": "Consultoria", "description": "Sessao de 1 hora" },
{ "id": "srv_2", "title": "Implementacao", "description": "Setup completo" },
{ "id": "srv_3", "title": "Suporte", "description": "Atendimento tecnico" }
]
}
]
}
}
}Response - 201 Created
{
"success": true,
"data": {
"message_id": "6f6e5d4c-3b2a-4190-8877-665544332211",
"status": "sent",
"phone": "+5511999990001",
"interactive_type": "button",
"timestamp": "2026-03-15T14:30:00.000Z"
}
}Fallback automatico para texto
Quando o canal WhatsApp não suporta mensagens interativas, o sistema envia automaticamente uma versao em texto numerado. Exemplo para botões:
Qual horário prefere para a visita?
1. Manha (9h-12h)
2. Tarde (14h-18h)
3. Noite (19h-21h)Evento de Calendario
O endpoint POST /v1/messages/event envia um card de evento nativo do WhatsApp que sincroniza com o calendario do celular do destinatario. Disponivel em alguns canais WhatsApp; nos demais, o sistema envia automaticamente uma versao em texto formatado.
Request
/v1/messages/eventScope necessário: messages:write
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| phone | string | Sim | Numero do destinatario com DDI |
| event.name | string | Sim | Título do evento |
| event.startTime | number | Sim | Unix timestamp em segundos. Deve ser no futuro |
| event.endTime | number | Nao | Unix timestamp. Deve ser maior que startTime |
| event.description | string | Nao | Descrição do evento |
| event.location | string | Nao | Local do evento |
| event.extraGuestsAllowed | boolean | Nao | Permitir convidados extras |
Exemplo
{
"phone": "+5511999990001",
"event": {
"name": "Reuniao de Alinhamento",
"description": "Revisao do projeto Q2",
"startTime": 1741017600,
"endTime": 1741021200,
"location": "Sala 3, Escritorio Central"
}
}Response - 201 Created
{
"success": true,
"data": {
"message_id": "6f6e5d4c-3b2a-4190-8877-665544332211",
"status": "sent",
"phone": "+5511999990001",
"event_name": "Reuniao de Alinhamento",
"start_time": 1741017600,
"end_time": 1741021200,
"timestamp": "2026-03-15T14:30:00.000Z"
}
}Enquete (Poll)
O endpoint POST /v1/messages/poll envia uma enquete nativa do WhatsApp com opções clicáveis. Ideal para pesquisas de satisfacao, qualificação e votacoes rapidas.
Request
/v1/messages/pollScope necessário: messages:write
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| phone | string | Sim | Numero do destinatario com DDI |
| poll.name | string | Sim | Pergunta da enquete |
| poll.options | string[] | Sim | Lista de opções clicáveis. Mínimo 2, máximo 12 |
| poll.multipleAnswers | boolean | Nao | Permitir selecao de multiplas respostas (default: false) |
Exemplo
{
"phone": "+5511999990001",
"poll": {
"name": "Qual o melhor dia para a entrega?",
"options": ["Segunda", "Terca", "Quarta", "Quinta", "Sexta"],
"multipleAnswers": true
}
}Response - 201 Created
{
"success": true,
"data": {
"message_id": "6f6e5d4c-3b2a-4190-8877-665544332211",
"status": "sent",
"phone": "+5511999990001",
"poll_name": "Qual o melhor dia para a entrega?",
"options_count": 5,
"timestamp": "2026-03-15T14:30:00.000Z"
}
}Criar Campanha - Referência Completa
Documentacao detalhada de todos os campos aceitos no endpoint POST /v1/campaigns.
POST /v1/campaigns HTTP/1.1
Host: api-worker.agilizai.tech
Authorization: Bearer ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"name": "Promocao de Verao",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"recipients": [
{
"phone": "+5511999990001",
"name": "Maria Silva"
},
{
"client_id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968"
}
],
"message": {
"text": "{{saudacao}}, {{primeiro_nome}}! Confira nossa promocao especial.",
"media_url": "https://cdn.exemplo.com/promo-verao.png",
"media_type": "image",
"media_delivery": "inline"
},
"options": {
"schedule_at": "2026-03-10T10:00:00Z",
"anti_ban_level": "seguro",
"callback_url": "https://meusite.com/webhook"
}
}Campos principais
namestringNome identificador da campanha. Aparece no historico e webhooks.
"Promocao de Verao"titlestringOpcional. Rótulo livre da campanha (até 120 caracteres).
"Black Friday — onda 1"descriptionstringOpcional. Descrição livre da campanha (até 500 caracteres).
"Envio para a base de inativos"setor_idstringOpcional. Numero único para envio. Se omitido, auto-seleciona.
"3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60"setor_idsstring[]Opcional. Lista de números para distribuição multi-numero. Alternativa a setor_id.
["3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60", "5c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"]distribution_modestringOpcional. "single" (um numero) ou "multi_number" (distribuição). Inferido automaticamente se omitido.
"multi_number"multi_numberbooleanOpcional. Se true, auto-resolve todos os números elegíveis para distribuição.
trueteam_idstringOpcional. UUID do time — modo de escopo próprio (pool = caixas do time). Mutuamente exclusivo com setor_id, setor_ids e multi_number.
"7c6d5e4f-3021-4213-8415-161718191a1b"recipientsarrayLista de destinatarios (min 1, max 10.000). Cada item deve ter phone ou client_id.
[{ "phone": "+5511999990001", "name": "Maria" }]message.textstringTexto da mensagem. Suporta variáveis com {{nome}}, {{primeiro_nome}}, etc.
"Ola {{nome}}, tudo bem?"Campos do destinatario (recipients[])
phonestringcondicionalNumero no formato E.164 com DDI. Obrigatório se client_id não for informado. Se o número não existir na base, um contato será criado automaticamente.
"+5511999990001"client_idstringcondicionalID de um contato já existente na base. Obrigatório se phone não for informado. Se informado junto com phone, o client_id tem prioridade.
"8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968"namestringopcionalNome do destinatario. Usado nas variáveis {{nome}} e {{primeiro_nome}}. Se omitido, usa o telefone.
"Maria Silva"textstringopcionalOverride por destinatario (planilha avançada V2): sobrepõe o texto da campanha só para esta pessoa (até 1024 caracteres).
"Ola Maria, condicao exclusiva pra voce!"media_urlstringopcionalOverride de mídia por destinatario. HTTPS. Exige media_type.
"https://cdn.exemplo.com/maria.png"media_typestringcondicionalTipo da mídia do override. Obrigatório com media_url. Valores: "image", "video", "audio", "document".
"image"variablesobjectopcionalMapa string→string que preenche placeholders do texto só para este destinatario.
{ "cupom": "MARIA10" }Campos da mensagem (message)
textstringcondicionalTexto principal da mensagem. Obrigatório se template_name não informado. Suporta variáveis automáticas: {{nome}}, {{primeiro_nome}}, {{saudacao}}, {{hora}}, {{data}}, {{telefone}}.
variantsstring[]opcionalVariações alternativas de texto (até 4, de 1024 caracteres cada). O sistema rotaciona entre o texto base e as variantes por destinatario para reduzir a impressão anti-spam. Incompatível com template_name.
media_urlstringopcionalURL pública da mídia a enviar (imagem, video, audio, documento). HTTPS obrigatório.
media_typestringcondicionalTipo da mídia. Obrigatório se media_url for informado. Valores: "image", "video", "audio", "document".
media_deliverystringopcionalOrdem da mídia com o texto. "inline" = legenda junto (padrão para imagem, video e document). "after_text" = texto primeiro, depois a mídia. "media_first" = mídia antes do texto. Áudio nunca usa legenda: só escolhe a ordem (split por padrão).
media_filenamestringopcionalNome do arquivo de mídia exibido ao destinatario (útil para document).
template_namestringcondicionalNome do template aprovado no Meta Business. Se informado, o campo text pode ser omitido.
template_languagestringcondicionalCódigo do idioma do template (ex: "pt_BR"). Obrigatório com template_name.
template_componentsarrayopcionalComponentes do template Meta (header, body, buttons) conforme especificacao da Cloud API.
template_rawarrayopcionalTemplate Meta bruto — o worker monta os components por destinatario a partir de template_vars.
template_varsstring[]opcionalNomes das variáveis do template preenchidas por destinatario (usado com template_raw).
template_name, template_language e template_components no objeto message, da mesma forma que em mensagens únicas. O campo text não e necessário quando se usa template.Opções avancadas (options)
schedule_atstring (ISO 8601)Agendar inicio da campanha. Se omitido, inicia imediatamente. Formato: "2026-03-10T10:00:00Z".
anti_ban_levelstringNivel de protecao anti-ban. Controla velocidade e delays entre mensagens.
distributionstringOrdem de envio quando há múltiplos números. "interleaved" (padrão, round-robin entre números) ou "sequential" (esgota um número antes do próximo). Só afeta multi-número.
callback_urlstringURL para receber webhook automatico ao finalizar esta campanha (alem dos webhooks globais configurados).
Niveis de anti_ban_level
normalIntervalo de 5 a 15s entre mensagens por número. Envio mais rápido — use com números já estabelecidos.
seguro(padrão)Intervalo de 20 a 40s. Recomendado para a maioria dos envios — bom equilíbrio entre velocidade e segurança.
lentoIntervalo de 45 a 90s. Cadência conservadora para listas grandes ou números em aquecimento.
muito_lentoIntervalo de 2 a 4 minutos. Máxima cautela para bases sensíveis.
ultra_lentoIntervalo de 5 a 8 minutos. Para número novo ou logo após uma restrição.
O intervalo efetivo respeita o piso de segurança do estágio de aquecimento do número. Valores legados moderate/aggressive equivalem a normal e conservative a seguro; valor inválido retorna 400 VALIDATION_ERROR.
Idempotencia
Para proteger contra campanhas duplicadas em caso de timeout ou retry de rede, inclua o header Idempotency-Key com um UUID único por requisição.
| Comportamento | Descrição |
|---|---|
| Primeira chamada | Cria a campanha normalmente e cacheia a resposta por 24 horas. |
| Chamadas seguintes | Retorna a resposta cacheada com meta.idempotent_replay: true. Nenhuma campanha duplicada e criada. |
POST /v1/campaigns HTTP/1.1
Host: api-worker.agilizai.tech
Authorization: Bearer ak_live_xxx
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/jsonDistribuição multi-numero
Campanhas podem ser distribuídas por múltiplos números WhatsApp para aumentar a capacidade e reduzir o risco de bloqueio.
| Cenario | Resultado |
|---|---|
Sem setor_id | Auto-seleciona. Se multi-número habilitado e vários números conectados, distribui automaticamente. |
setor_id (singular) | Usa esse numero. Modo single. |
setor_ids (array) | Distribui entre os números informados. |
multi_number: true | Auto-resolve todos os números elegíveis e distribui. |
A resposta inclui distribution_mode ("single" ou "multi_number") e number_pool_size indicando quantos números serão usados.
Horário seguro obrigatório
Campanhas imediatas (sem schedule_at) fora do horário 8h–21h (Brasília) são rejeitadas com 403 SAFE_HOUR_VIOLATION. A resposta traz tudo que você precisa pra agendar: o header HTTP Retry-After (em segundos) e, em details.next_allowed_at, o instante ISO 8601 UTC que pode ser copiado direto pra options.schedule_at. Os horários são configuráveis por empresa e visíveis em GET /v1/limits.
HTTP/1.1 403 Forbidden
Retry-After: 31500
Content-Type: application/json
{
"success": false,
"error": {
"code": "SAFE_HOUR_VIOLATION",
"message": "Envios em massa só são permitidos entre 08:00 e 21:00 (horário de Brasília). Agora são 22:15 (Brasília). Use `options.schedule_at` para agendar dentro da próxima janela (2026-05-27T08:00:00-03:00).",
"details": {
"safe_hour_start": 8,
"safe_hour_end": 21,
"current_hour_brasilia": 22,
"current_minute_brasilia": 15,
"current_time_brasilia": "2026-05-26T22:15:00-03:00",
"next_allowed_at": "2026-05-27T11:00:00.000Z",
"next_allowed_at_brasilia": "2026-05-27T08:00:00-03:00",
"suggested_schedule_at": "2026-05-27T11:00:00.000Z",
"retry_after_seconds": 31500,
"docs_url": "https://dashboard.agilizai.tech/api-docs#schedule_at"
}
}
}Como tratar: ao receber este erro, leia details.suggested_schedule_at e reenvie a mesma campanha com options.schedule_at = esse valor. Alternativamente, respeite o header Retry-After e tente novamente quando a janela abrir.
Limite de campanhas simultâneas
Cada empresa tem um teto de campanhas simultâneas (pendente + processando). Quando o teto é atingido, novas chamadas a POST /v1/campaigns são rejeitadas com 403 CONCURRENT_LIMIT_EXCEEDED. A resposta traz tudo que você precisa pra destravar: a lista (até 10) das campanhas que ocupam os slots em details.active_campaigns e, se alguma das ativas for agendada futura, o ISO 8601 UTC do próximo slot em details.next_slot_eta + o header HTTP Retry-After (em segundos). O limite vigente está em GET /v1/limits.
HTTP/1.1 403 Forbidden
Retry-After: 54000
Content-Type: application/json
{
"success": false,
"error": {
"code": "CONCURRENT_LIMIT_EXCEEDED",
"message": "Limite de campanhas simultâneas atingido (3). Aguarde a próxima conclusão ou a campanha agendada para 2026-05-27T08:00:00-03:00 ou conclua/cancele alguma das 3 ativas.",
"details": {
"limit": 3,
"active": 3,
"active_campaigns": [
{
"id": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"titulo": "Black Friday — onda 1",
"status": "processando",
"scheduled_for": null,
"created_at": "2026-05-26T19:42:11.000Z"
},
{
"id": "3b4c5d6e-7f80-4912-ab2c-3d4e5f607182",
"titulo": "Black Friday — onda 2",
"status": "pendente",
"scheduled_for": "2026-05-27T11:00:00.000Z",
"created_at": "2026-05-26T19:55:00.000Z"
},
{
"id": "4c5d6e7f-8091-4a23-bc3d-4e5f60718293",
"titulo": "Reativação inativos",
"status": "pendente",
"scheduled_for": "2026-05-28T11:00:00.000Z",
"created_at": "2026-05-26T20:10:00.000Z"
}
],
"next_slot_eta": "2026-05-27T11:00:00.000Z",
"retry_after_seconds": 54000,
"docs_url": "https://dashboard.agilizai.tech/api-docs#concurrent_limit"
}
}
}Como tratar: existem três caminhos. (1) Aguardar — respeite o header Retry-After e tente novamente quando o próximo slot abrir. (2) Liberar slot — chame POST /v1/campaigns/{id}/cancel em uma das campanhas listadas em details.active_campaigns. (3) Aumentar o teto — peça ao administrador da empresa pra ajustar envios_massa_max_concurrent no plano (campanhas agendadas para o futuro continuam contando como ativas enquanto não rodam).
Resposta (202 Accepted)
A campanha e enfileirada para processamento assíncrono. O status inicial e "pendente".
{
"success": true,
"data": {
"id": "7d8e9f0a-1b2c-3d4e-5f60-7a8b9c0d1e2f",
"status": "queued",
"total_recipients": 2,
"credits_reserved": 2,
"created_at": "2026-03-10T10:00:00Z",
"execution_mode": "immediate",
"scheduled_for": null,
"waiting_reason": null,
"distribution_mode": "single",
"number_pool_size": 1
}
}pendente (aguardando processamento), processando (enviando), pausado, concluido e falha. Na resposta de criação, queued equivale a pendente.immediate (inicio imediato), scheduled (agendado via schedule_at), ou deferred_to_safe_window (adiado para o próximo horário seguro, com waiting_reason: "outside_safe_hours").Limites de envio
- Max destinatarios: 10.000 por campanha.
- Limite diario: Configurado por empresa (padrão 5.000/dia; Trial/plano básico 500). Verificado ao criar campanha.
- Campanhas simultâneas: Limite configurado por empresa (padrão 3; Trial/plano básico 1). Aguarde uma finalizar ou veja os planos.
- Numero conectado: O setor deve ter um WhatsApp conectado (numero_contato configurado).
Personalizacao de Mensagens
Use variáveis no texto da mensagem para personalizar cada envio automaticamente. A personalizacao aumenta o engajamento e reduz o risco de bloqueio.
Variaveis automáticas
Substituidas automaticamente com dados do destinatario e contexto.
| Variavel | Valor | Exemplo |
|---|---|---|
{{nome}} | Nome completo do destinatario | Maria Silva |
{{primeiro_nome}} | Primeiro nome do destinatario | Maria |
{{telefone}} | Numero de telefone | +5511999990001 |
{{saudacao}} | Saudacao automatica por horário (Bom dia / Boa tarde / Boa noite) | Bom dia |
{{hora}} | Hora atual no formato HH:MM | 14:30 |
{{data}} | Data atual no formato DD/MM/AAAA | 06/03/2026 |
Variacao automatica (anti-spam)
O sistema aplica automaticamente duas camadas de variacao para evitar deteccao de spam pelo WhatsApp:
O sistema personaliza automaticamente as saudacoes para manter as mensagens naturais e únicas.
Variacao visivel: A variável {{saudacao}} alterna automaticamente entre formas como "Bom dia", "Ola, bom dia" e "Oi! Bom dia" para parecer mais natural.
Essas variacoes sao aplicadas automaticamente. Você não precisa fazer nada — apenas use as variáveis normalmente.
Ciclo de Vida da Campanha
Uma campanha passa por diferentes status durante sua execução. Você pode pausar, retomar ou cancelar em qualquer momento.
Fluxo de status
POST /v1/campaigns/:id/pause-- Pausar campanha
Pausa uma campanha em andamento (status "processando"). As mensagens ja enviadas não sao afetadas. Mensagens pendentes ficam em espera ate você retomar.
POST /v1/campaigns/2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071/pause
// Resposta:
{
"success": true,
"data": { "status": "paused" }
}
// Webhook disparado: campaign.pausedPOST /v1/campaigns/:id/resume-- Retomar campanha
Retoma uma campanha pausada. A campanha e re-enfileirada e continua enviando apenas as mensagens que ainda estao pendentes. Mensagens já enviadas ou com falha não sao reprocessadas.
POST /v1/campaigns/2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071/resume
// Resposta:
{
"success": true,
"data": {
"status": "processing",
"pending_requeued": 158
}
}POST /v1/campaigns/:id/cancel-- Cancelar campanha
Cancela uma campanha em status "processando" ou "pausado". Todas as mensagens pendentes sao marcadas como "pulado" (skipped) com motivo "cancelled_by_user". Mensagens já enviadas permanecem inalteradas. A campanha muda para status "concluido".
POST /v1/campaigns/2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071/cancel
// Resposta:
{
"success": true,
"data": {
"cancelled": 158
}
}
// Webhook disparado: campaign.completed (com cancelledByUser: true)pause em vez de cancel.Acompanhamento e Historico
Construa uma tela de monitoramento em tempo real e historico completo dos envios usando os endpoints de leitura. Combine com webhooks para atualizacoes instantaneas.
GET /v1/campaigns-- Historico de campanhas
Lista todas as campanhas com paginação. Filtre por status para montar abas (ativas, concluidas, falhas).
GET /v1/campaigns?page=1&limit=20&status=concluido
// Resposta:
{
"success": true,
"data": {
"items": [
{
"id": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"mensagem": "Ola {{nome}}! Confira nossa promocao.",
"total_destinatarios": 500,
"total_enviados": 487,
"total_falhas": 8,
"total_pulados": 5,
"status": "concluido",
"created_at": "2026-03-05T10:00:00Z"
},
{
"id": "3b4c5d6e-7f80-4912-ab2c-3d4e5f607182",
"mensagem": "Lembrete: seu agendamento amanha.",
"total_destinatarios": 120,
"total_enviados": 120,
"total_falhas": 0,
"total_pulados": 0,
"status": "concluido",
"created_at": "2026-03-04T14:30:00Z"
}
],
"total": 47,
"page": 1,
"limit": 20,
"totalPages": 3
}
}status (pendente, processando, concluido, falha, pausado), page, limit (max 100).GET /v1/campaigns/:id-- Progresso em tempo real
Retorna os contadores atualizados e o percentual de progresso. Use polling (5-10s) para atualizar uma barra de progresso.
GET /v1/campaigns/2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071
// Resposta:
{
"success": true,
"data": {
"id": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"mensagem": "Ola {{nome}}! Confira nossa promocao.",
"imagem_url": null,
"total_destinatarios": 500,
"total_enviados": 342,
"total_falhas": 5,
"total_pulados": 3,
"status": "processando",
"progress": 70,
"created_at": "2026-03-05T10:00:00Z",
"updated_at": "2026-03-05T10:15:32Z"
}
}progress e calculado como (enviados + falhas + pulados) / total * 100. Quando chegar a 100%, o status será "concluido".GET /v1/campaigns/:id/messages-- Detalhes por destinatario
Lista o status de cada mensagem individual. Use para construir uma tabela de detalhes com nome, telefone, status e erro (se houver).
GET /v1/campaigns/2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071/messages?page=1&limit=50&status=falha
// Resposta:
{
"success": true,
"data": {
"items": [
{
"id": "11112222-3333-4444-8555-666677778888",
"status": "falha",
"erro": "HTTP 503: Service Unavailable",
"erro_codigo": "TEMP_SERVER_ERROR",
"erro_legivel": "Serviço temporariamente indisponível",
"created_at": "2026-03-05T10:00:00Z",
"sent_at": null,
"assigned_setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"actual_setor_id": null,
"clientes": {
"nome": "Joao Silva",
"telefone": "+5511999990001"
}
},
{
"id": "22223333-4444-4555-9666-777788889999",
"status": "falha",
"erro": "warming_daily_limit",
"erro_codigo": "WARMING_DAILY_LIMIT",
"erro_legivel": "Limite de aquecimento do número WhatsApp atingido para hoje.",
"created_at": "2026-03-05T10:00:00Z",
"sent_at": null,
"assigned_setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"actual_setor_id": null,
"clientes": {
"nome": "Maria Santos",
"telefone": "+5521888880002"
}
}
],
"total": 5,
"page": 1,
"limit": 50,
"totalPages": 1
}
}pendente, enviado, falha, pulado. Sem filtro retorna todos. Paginacao ate 200 por pagina.Como construir uma tela de acompanhamento
Listagem (historico): Use GET /v1/campaigns com filtros de status para criar abas: Ativas, Concluidas, Falhas.
Progresso (polling): Ao clicar numa campanha ativa, faca polling em GET /v1/campaigns/:id a cada 5-10 segundos para atualizar a barra de progresso e contadores.
Detalhes (tabela): Use GET /v1/campaigns/:id/messages para listar cada destinatario com nome, telefone, status e motivo de erro. Filtre por status para ver apenas falhas ou pendentes.
Tempo real (webhooks): Em vez de polling, configure um webhook para os eventos message.sent, message.failed e campaign.completed para receber atualizacoes instantaneas e atualizar a UI via WebSocket.
Exemplo: Polling de progresso (Node.js)
async function pollCampaignProgress(campaignId, apiKey) {
const BASE = 'https://api-worker.agilizai.tech';
while (true) {
try {
const res = await fetch(`${BASE}/v1/campaigns/${campaignId}`, {
headers: { 'Authorization': `Bearer ${apiKey}` },
});
const { data: campaign } = await res.json();
console.log(
`Progresso: ${campaign.progress}% | ` +
`Enviados: ${campaign.total_enviados}/${campaign.total_destinatarios} | ` +
`Falhas: ${campaign.total_falhas} | Status: ${campaign.status}`
);
// Parar quando concluido ou falhou (cancelamento também gera status 'concluido')
if (['concluido', 'falha'].includes(campaign.status)) {
console.log('Campanha finalizada!');
// Buscar mensagens que falharam
if (campaign.total_falhas > 0) {
const failedRes = await fetch(
`${BASE}/v1/campaigns/${campaignId}/messages?status=falha`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
const failedPayload = await failedRes.json();
const items = failedPayload.data.items;
console.log('Falhas:', items.map(m =>
`${m.clientes.telefone}: ${m.erro}`
));
}
break;
}
} catch (error) {
console.error('Erro ao consultar campanha:', error.message);
}
await new Promise(r => setTimeout(r, 5000)); // 5s entre cada poll
}
}Conexão WhatsApp via API
Para enviar mensagens, você precisa conectar um número WhatsApp. O fluxo completo pode ser feito via API, sem precisar acessar o dashboard.
1.Criar instancia
Crie a conexão do canal para o setor. O canal e resolvido automaticamente pela configuração da empresa e o proxy e configurado automaticamente quando necessário.
POST /v1/whatsapp/instances
{
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60"
}
// Resposta 201:
{
"success": true,
"data": {
"instance_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"status": "disconnected",
"proxy_configured": true,
"reused": false
}
}2.Obter QR Code
Obtenha o QR code e exiba-o no seu sistema para o usuario escanear com o WhatsApp.
GET /v1/whatsapp/instances/3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60/qr
// Resposta:
{
"success": true,
"data": {
"qr_code": "data:image/png;base64,iVBOR...",
"expires_in": 60
}
}O campo expires_in indica o tempo em segundos. O QR code expira em aproximadamente 60 segundos. Solicite um novo se expirar.
3.Aguardar conexão (polling)
Faca polling no endpoint de status ate o número estar conectado. Intervalo recomendado: 3-5 segundos.
GET /v1/whatsapp/instances/3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60/status
// Resposta durante escaneamento:
{ "success": true, "data": { "status": "connecting", "phone_number": null } }
// Resposta após conectar:
{ "success": true, "data": { "status": "connected", "phone_number": "+5511999990001" } }4.Pronto para disparar
Com status "connected", o setor esta pronto para criar campanhas.
POST /v1/campaigns
{
"name": "Primeira campanha",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60", // mesmo setor conectado
"recipients": [...],
"message": { "text": "Ola {{nome}}!" }
}QR Code expira rapidamente
O QR code expira em ~60 segundos. Se expirar, faça uma nova requisição para /qr. Para desconectar, use POST /v1/whatsapp/instances/:id/disconnect.
GET /v1/whatsapp/instances/health-- Saude das instancias
Retorna o status de saude de todas as instancias WhatsApp da empresa. Use para verificar quais números estao conectados antes de criar campanhas.
GET /v1/whatsapp/instances/health?include_history=false
// Resposta:
{
"success": true,
"data": {
"items": [
{
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"setor_nome": "Vendas",
"phone_number": "+5511999990001",
"health": {
"state": "healthy",
"connected": true,
"responding": true,
"lastCheckedAt": "2026-03-06T15:00:00Z",
"lastOkAt": "2026-03-06T15:00:00Z",
"consecutiveFailures": 0,
"error": null
}
},
{
"setor_id": "5c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"setor_nome": "Suporte",
"phone_number": null,
"health": {
"state": "down",
"connected": false,
"responding": false,
"lastCheckedAt": "2026-03-06T15:00:00Z",
"lastOkAt": null,
"consecutiveFailures": 5,
"error": "Instance not connected"
}
}
]
}
}health.state assume um de: healthy, degraded, down ou unknown. Uma instancia sem conexão vem com connected: false e state down / unknown.
Query params opcionais: include_history (boolean, default false) e history_minutes (1-1440, default 60).
Limites e cobrança
Consulte os limites efetivos da empresa, a capacidade disponível no dia e o saldo avulso atualmente disponível.
O que o endpoint informa
Habilitação: Informa se o envio em massa está ativo para a empresa e qual modalidade está em vigor.
Capacidade: Mostra limite diário, campanhas simultâneas ativas e RPM efetivo da chave.
Saldo avulso: Quando a empresa opera em modalidade avulsa, o saldo disponível aparece em `avulso_balance`.
GET /v1/limits-- Limites e capacidade
Retorna os limites efetivos da empresa, capacidade restante do dia, concorrencia ativa e RPM efetivo da chave.
GET /v1/limits
// Resposta:
{
"success": true,
"data": {
"bulk_messaging_enabled": true,
"modality": "mensal",
"plan_code": "crm-pro",
"expires_at": null,
"unlimited": false,
"avulso_balance": 0,
"daily": {
"limit": 1000,
"sent_today": 240,
"remaining": 760
},
"concurrent_campaigns": {
"limit": 3,
"active": 1,
"remaining": 2
},
"max_recipients_per_campaign": 10000,
"rate_limit": {
"api_key_rpm": 60,
"company_rpm": 60,
"effective_rpm": 60
},
"safe_hours": {
"start": 8,
"end": 21
}
}
}unlimited -- Booleano sempre presente. Quando true (empresa ilimitada/legada), o envio não é limitado por saldo.
avulso_balance -- Saldo restante de mensagens pre-pagas (modelo avulso). Cada mensagem enviada desconta 1 unidade. No modelo mensal, sem saldo pre-pago, e 0. Para empresa unlimited vem -1 (sentinela de ilimitado, não saldo zerado).
safe_hours -- Horario permitido para envio de campanhas (fuso BRT). Campanhas agendadas fora desse horário serão iniciadas automaticamente no próximo horário permitido. Exemplo: start: 8, end: 21 significa que envios ocorrem apenas entre 8h e 21h.
Webhooks
Receba notificações em tempo real quando eventos ocorrerem na sua conta. Todos os payloads sao assinados com HMAC-SHA256 para garantir autenticidade.
Para confirmar que uma requisição realmente veio da Agilizai (e não de terceiros), cada POST inclui uma assinatura digital no header. Você pode usar o secret recebido na criação do webhook para validar essa assinatura. Essa verificação e opcional mas recomendada em producao.
Verificacao de assinatura
Cada requisição de webhook inclui 3 headers de segurança. Sempre verifique a assinatura antes de processar o evento.
X-Webhook-Id -- ID único da entrega (para deduplicacao).
X-Webhook-Timestamp -- Timestamp Unix (segundos) de quando o webhook foi enviado.
X-Webhook-Signature -- HMAC-SHA256 de {timestamp}.{payload} usando seu webhook secret.
Exemplo de verificação (Node.js / Express)
const crypto = require('crypto');
function verifyWebhookSignature(signedPayload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
if (Buffer.from(signature).length !== Buffer.from(expected).length) {
return false;
}
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Express middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const webhookId = req.headers['x-webhook-id']; // ID único da entrega
const timestamp = req.headers['x-webhook-timestamp']; // Unix timestamp (segundos)
const signature = req.headers['x-webhook-signature']; // HMAC-SHA256
const payload = req.body.toString();
const signedPayload = `${timestamp}.${payload}`;
if (!verifyWebhookSignature(signedPayload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Opcional: rejeitar webhooks com timestamp muito antigo (replay attack)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (age > 300) { // 5 minutos
return res.status(401).json({ error: 'Timestamp too old' });
}
const event = JSON.parse(payload);
console.log('Webhook event:', event.event, 'ID:', webhookId);
switch (event.event) {
case 'campaign.completed':
// Campanha finalizada
break;
case 'message.sent':
// Mensagem enviada com sucesso
break;
case 'message.failed':
// Falha ao enviar mensagem
break;
}
res.status(200).json({ received: true });
});Exemplo de verificação (Python / Flask)
import hmac
import hashlib
import time
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "seu_webhook_secret"
def verify_signature(payload: bytes, timestamp: str, signature: str) -> bool:
signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
expected = hmac.new(
WEBHOOK_SECRET.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route("/webhook", methods=["POST"])
def webhook():
webhook_id = request.headers.get("X-Webhook-Id")
timestamp = request.headers.get("X-Webhook-Timestamp", "")
signature = request.headers.get("X-Webhook-Signature", "")
if not verify_signature(request.data, timestamp, signature):
return jsonify({"error": "Invalid signature"}), 401
# Rejeitar timestamps antigos (> 5 min)
age = int(time.time()) - int(timestamp)
if age > 300:
return jsonify({"error": "Timestamp too old"}), 401
event = request.json
print(f"Webhook {webhook_id}: {event['event']}")
return jsonify({"received": True}), 200Referência de respostas dos endpoints de webhook
POST /v1/webhooks — 201 Created
{
"success": true,
"data": {
"id": "e0f1a2b3-c4d5-4667-8899-aabbccddeeff",
"url": "https://meusite.com/webhook",
"events": ["campaign.completed", "message.failed"],
"secret": "a1b2c3...hex64chars...",
"active": true,
"created_at": "2026-03-06T10:00:00Z"
}
}O campo secret e retornado apenas na criação. Armazene-o em local seguro.
GET /v1/webhooks — 200 OK
{
"success": true,
"data": {
"items": [
{
"id": "e0f1a2b3-c4d5-4667-8899-aabbccddeeff",
"url": "https://meusite.com/webhook",
"events": ["campaign.completed", "message.failed"],
"active": true,
"created_at": "2026-03-06T10:00:00Z",
"updated_at": "2026-03-06T12:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 1,
"totalPages": 1
}
}O campo secret não e exposto na listagem.
PUT /v1/webhooks/:id — 200 OK
Todos os campos sao opcionais: url, events, active.
{
"success": true,
"data": {
"id": "e0f1a2b3-c4d5-4667-8899-aabbccddeeff",
"url": "https://meusite.com/webhook-v2",
"events": ["campaign.completed"],
"active": true,
"updated_at": "2026-03-06T14:00:00Z"
}
}DELETE /v1/webhooks/:id — 200 OK
{
"success": true,
"data": {
"deleted": true,
"id": "e0f1a2b3-c4d5-4667-8899-aabbccddeeff"
}
}Eventos disponíveis
campaign.startedEnvio iniciado
campaign.completedTodos os envios finalizados
campaign.failedCampanha falhou
campaign.pausedCampanha pausada
message.sentMensagem enviada com sucesso
message.deliveredMensagem entregue ao destinatario
message.readMensagem lida pelo destinatario
message.failedFalha ao enviar mensagem
message.receivedMensagem recebida do contato
conversation.createdNovo atendimento (ticket) criado
conversation.assignedAtendimento atribuido a um agente
conversation.ratedAtendimento avaliado pelo contato
contact.createdContato criado
contact.taggedEtiqueta aplicada ao contato
contact.mergedContatos duplicados unificados
appointment.createdAgendamento criado
appointment.rescheduledAgendamento remarcado
appointment.cancelledAgendamento cancelado
opportunity.createdOportunidade criada no funil
opportunity.wonOportunidade ganha
opportunity.lostOportunidade perdida
contract.sentContrato enviado para assinatura
contract.signedContrato assinado
contract.refusedContrato recusado
lead.search_completedBusca de leads B2B concluida
lead.saved_to_crmLeads salvos no CRM
import.completedImportacao de contatos concluida
import.failedImportacao de contatos falhou
payment.receivedPagamento recebido
billing.low_balanceSaldo de creditos baixo
whatsapp.session_connectedNumero WhatsApp conectado
whatsapp.session_disconnectedNumero WhatsApp desconectado
Configurações > Webhooks.Formato do payload
Todo POST de entrega inclui o mesmo envelope de topo: event, version ("v1"), timestamp, empresa_id, setor_id (pode ser null em eventos sem caixa, ex.: CRM/billing), data e actor ({ user_id, source }). O campo data varia conforme o evento.
campaign.completed
{
"event": "campaign.completed",
"version": "v1",
"timestamp": "2026-03-06T15:30:00.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"totalDestinatarios": 500,
"totalEnviados": 487,
"totalFalhas": 8,
"totalPulados": 5,
"cancelledByUser": false
},
"actor": { "user_id": null, "source": "system" }
}cancelledByUser— true quando a campanha foi cancelada manualmente, false quando concluiu normalmente
message.sent
{
"event": "message.sent",
"version": "v1",
"timestamp": "2026-03-06T15:25:12.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"detailId": "11112222-3333-4444-8555-666677778888",
"recipientPhone": "+5511999990001",
"recipientName": "Maria Silva",
"status": "sent",
"error": null,
"error_code": null,
"actual_setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"was_reassigned": false
},
"actor": { "user_id": null, "source": "system" }
}detailId— ID único desta mensagem na campanhaerror_code— código do erro (ex: SENDER_DISCONNECTED, RECIPIENT_NOT_FOUND). Null quando enviado com sucessoactual_setor_id— qual número WhatsApp enviou a mensagem (pode diferir se houver redistribuição automatica)was_reassigned— indica se a mensagem foi redirecionada para outro número automaticamente
message.failed
{
"event": "message.failed",
"version": "v1",
"timestamp": "2026-03-06T15:25:45.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"detailId": "44445555-6666-4777-a888-9999aaaabbbb",
"recipientPhone": "+5521888880002",
"recipientName": "Joao Santos",
"status": "failed",
"error": "Numero não encontrado no WhatsApp",
"error_code": "RECIPIENT_NOT_FOUND",
"actual_setor_id": null,
"was_reassigned": false
},
"actor": { "user_id": null, "source": "system" }
}detailId— ID único desta mensagem na campanhaerror_code— código do erro (ex: SENDER_DISCONNECTED, RECIPIENT_NOT_FOUND). Null quando enviado com sucessoactual_setor_id— qual número WhatsApp enviou a mensagem (pode diferir se houver redistribuição automatica)was_reassigned— indica se a mensagem foi redirecionada para outro número automaticamente
campaign.started
{
"event": "campaign.started",
"version": "v1",
"timestamp": "2026-03-06T15:00:00.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"setorId": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"userId": "33334444-5555-4666-b777-88889999aaaa"
},
"actor": { "user_id": null, "source": "system" }
}campaign.paused
{
"event": "campaign.paused",
"version": "v1",
"timestamp": "2026-03-06T15:20:00.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071"
},
"actor": { "user_id": null, "source": "system" }
}campaign.failed
{
"event": "campaign.failed",
"version": "v1",
"timestamp": "2026-03-06T15:30:00.000Z",
"empresa_id": "0a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"data": {
"campaignId": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6071",
"error": "Instancia WhatsApp desconectada"
},
"actor": { "user_id": null, "source": "system" }
}Politica de retry
Se seu endpoint retornar erro (5xx ou timeout), o webhook será reenviado automaticamente com backoff exponencial:
Apos 5 tentativas sem sucesso, o webhook e marcado como falho. Erros 4xx (exceto 429) não sao retentados. Seu endpoint deve responder com 2xx em ate 5 segundos.
O endpoint POST /v1/webhooks/:id/test também aplica timeout de 5 segundos. Se seu endpoint não responder dentro desse prazo, o teste será considerado falho (abort).
Códigos de Erro
A API utiliza códigos HTTP padrão. Respostas de erro seguem o envelope JSON do contrato público, com success: false e um objeto error contendo código e descrição legivel.
{
"success": false,
"error": {
"code": "BAD_REQUEST",
"message": "name and recipients[] are required"
}
}| Código | Nome | Descrição |
|---|---|---|
| 400 | Bad Request | A requisição contém dados inválidos. Verifique o corpo da requisição. |
| 401 | Unauthorized | Token de API ausente ou inválido. Verifique o header Authorization. |
| 402 | Payment Required | Cota ou saldo esgotado. Recarregue créditos ou faça upgrade do plano para continuar. |
| 403 | Forbidden | Sua API key não possui o scope necessário para este endpoint. |
| 404 | Not Found | O recurso solicitado não existe ou foi removido. |
| 429 | Too Many Requests | Limite de requisicoes excedido. Aguarde antes de tentar novamente. |
| 500 | Internal Server Error | Erro interno do servidor. Tente novamente ou entre em contato com o suporte. |
| 502 | Provider Error | Falha (5xx ou rede) no provedor de WhatsApp ao processar a requisição. Tente novamente. |
Códigos de erro classificados
Códigos retornados no campo erro_codigo nas mensagens de campanha e no campo error_code nos webhooks de falha.
Erros do remetente
| Código | Descrição |
|---|---|
SENDER_BANNED | Número de envio bloqueado pelo WhatsApp |
SENDER_CORRUPT | Sessão do número corrompida |
SENDER_REPLACED | Sessão do número substituída por outro dispositivo |
SENDER_RESTRICTED | Número de envio com restrições |
SENDER_DISCONNECTED | WhatsApp do número de envio está offline |
SENDER_AUTH | Número de envio sem autorização |
Erros do destinatario
| Código | Descrição |
|---|---|
RECIP_NOT_WHATSAPP | Destinatário não tem WhatsApp |
RECIP_BLOCKED | Destinatário bloqueou este número |
RECIP_INVALID | Número do destinatário inválido |
META_WINDOW_EXPIRED | Janela de 24h expirada para este destinatário |
META_NOT_ELIGIBLE | Destinatário não pode receber mensagens |
Erros temporarios
| Código | Descrição |
|---|---|
TEMP_RATE_LIMIT | Limite de velocidade atingido |
TEMP_TIMEOUT | Tempo de resposta excedido |
TEMP_NETWORK | Erro de conexão temporário |
TEMP_SERVER_ERROR | Serviço temporariamente indisponível |
TEMP_RECONNECTING | Reconectando o número |
Outros
| Código | Descrição |
|---|---|
UNKNOWN | Erro não identificado no envio |
Planos e Limites (Envio em Massa)
O envio em massa é habilitado por empresa e opera por modalidade mensal ou avulso. O endpoint GET /v1/limits é a fonte de verdade para o cliente externo saber se pode enviar e quais limites aplicar.
| Modalidade | Comportamento | Regra de bloqueio |
|---|---|---|
| mensal | Envio ativo enquanto a empresa estiver habilitada no admin. | Bloqueia quando estoura limite diário, concorrência ativa ou RPM. |
| avulso | Envio ativo até expires_at. | Se expirar, novas campanhas retornam 403. |
| inativo | Recurso desabilitado para a empresa. | Criação de campanha bloqueada com 403. |
daily, concurrent_campaigns, rate_limit, safe_hours).Limites e Rate Limits
A API possui dois tipos de limites: limites de requisicoes (rate limit) e limites de envio (volume de mensagens). Ambos sao configurados individualmente por empresa no painel administrativo.
Rate Limit (requisicoes API)
Limita o número de chamadas HTTP por minuto. Dois niveis sao aplicados simultaneamente:
Por API Key
60 req/min
Configuravel individualmente por API key. Cada API key tem seu proprio contador.
Por Empresa
60 req/min
Agregado de todas as keys da empresa. Se a empresa tiver varias keys, o limite total da empresa também se aplica.
Limites de Envio (mensagens)
Controlam o volume de mensagens que podem ser enviadas. Sao verificados automaticamente ao criar uma campanha e durante o processamento.
| Limite | Padrao | Descrição | Erro retornado |
|---|---|---|---|
Mensagens / dia | 5.000 | Total de mensagens enviadas com sucesso no dia. Ao criar campanha, verifica se (enviados_hoje + novos_destinatarios) excede o limite. Trial/plano básico mantêm 500/dia. | 403: Daily messages limit exceeded |
Campanhas simultâneas | 3 | Numero máximo de campanhas com status "pendente" ou "processando" ao mesmo tempo. Aguarde uma finalizar ou cancele antes de criar outra. Trial/plano básico mantêm 1. | 403: Concurrent campaigns limit reached |
Destinatarios / campanha | 10.000 | Numero máximo de destinatarios em uma única campanha. Para volumes maiores, divida em multiplas campanhas. | 400: Maximum 10000 recipients |
Warming diario | 50 - 1000 | O limite por número WhatsApp e baseado no historico de uso. Numeros novos tem limites mais baixos que crescem gradualmente com o uso responsavel. | Mensagem pulada: warming_daily_limit |
GET /v1/limits-- Consultar seus limites
Retorna todos os limites configurados para sua empresa, incluindo uso atual no dia. Use este endpoint para validar antes de criar campanhas e exibir informações na sua interface.
GET /v1/limits
// Resposta:
{
"success": true,
"data": {
"bulk_messaging_enabled": true,
"modality": "mensal",
"plan_code": "crm-pro",
"expires_at": null,
"unlimited": false,
"avulso_balance": 0,
"daily": {
"limit": 5000,
"sent_today": 342,
"remaining": 4658
},
"concurrent_campaigns": {
"limit": 3,
"active": 0,
"remaining": 3
},
"max_recipients_per_campaign": 10000,
"rate_limit": {
"api_key_rpm": 60,
"company_rpm": 60,
"effective_rpm": 60
},
"safe_hours": {
"start": 8,
"end": 21
}
}
}daily.remaining -- Quantas mensagens ainda podem ser enviadas hoje.
concurrent_campaigns.remaining -- Quantas campanhas podem ser criadas agora (se 0, aguarde uma finalizar).
rate_limit.effective_rpm -- O limite efetivo de requisicoes API por minuto (menor entre key e empresa).
GET /v1/limits antes de criar uma campanha para verificar se tem capacidade disponível. Se daily.remaining for menor que o número de destinatarios, a campanha será rejeitada com erro 403.Modalidade de acesso
O envio em massa precisa estar habilitado para sua empresa. Existem duas modalidades:
Plano mensal
Acesso continuo enquanto a assinatura estiver ativa. Limites definidos pelo plano contratado.
Avulso
Acesso temporario com data de expiracao. Apos expirar, novas campanhas sao bloqueadas (erro 403: "Plano avulso expirado").
Headers de Rate Limit
Cada resposta inclui headers indicando seu uso atual:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1706817600X-RateLimit-Limit -- Numero máximo de requisicoes permitidas na janela (menor entre key e empresa).
X-RateLimit-Remaining -- Requisicoes restantes na janela atual.
X-RateLimit-Reset -- Timestamp (Unix) de quando a janela será reiniciada.
Ao receber 429 Too Many Requests
A resposta informa se o limite excedido foi da key ou da empresa. Aguarde o tempo indicado no campo retryAfter (em segundos) antes de tentar novamente.
{
"success": false,
"error": {
"code": "TOO_MANY_REQUESTS",
"message": "Rate limit exceeded. Key: 60/min, Company: 30/min.",
"retryAfter": 60
}
}Exemplos de Código
Exemplo completo de criação de uma campanha de envio em massa em diferentes linguagens.
Envio de mensagem única
POST /v1/messages -- Envia uma mensagem individual de forma sincrona.
curl -X POST https://api-worker.agilizai.tech/v1/messages \
-H "Authorization: Bearer ak_live_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999999999",
"message": {
"text": "Ola! Seu pedido #1234 foi confirmado."
}
}'Criar campanha
POST /v1/campaigns -- Cria uma campanha com agendamento e personalizacao automatica.
curl -X POST https://api-worker.agilizai.tech/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Promocao Janeiro",
"setor_id": "3f9a2b1c-8d7e-4c6b-9a0f-1e2d3c4b5a60",
"message": { "text": "Ola {{nome}}! Confira nossa promocao especial." },
"recipients": [
{ "phone": "+5511999999999", "name": "Maria" },
{ "phone": "+5521888888888", "name": "Joao" }
],
"options": {
"schedule_at": "2026-01-15T10:00:00Z",
"anti_ban_level": "seguro"
}
}'Resposta esperada
202 Accepted
{
"success": true,
"data": {
"id": "7d8e9f0a-1b2c-3d4e-5f60-7a8b9c0d1e2f",
"status": "queued",
"total_recipients": 2,
"credits_reserved": 2,
"created_at": "2026-01-10T14:30:00Z",
"execution_mode": "immediate",
"scheduled_for": null,
"waiting_reason": null,
"distribution_mode": "single",
"number_pool_size": 1
}
}Pronto para integrar?
Crie sua API key no dashboard e comece a enviar campanhas programaticamente em minutos.