Zivlo API v1

Documentação da API

A API Zivlo permite enviar mensagens WhatsApp e disparar fluxos de automação programaticamente. Use-a para integrar com seus sistemas, CRMs, plataformas de agendamento e muito mais.

BASE URL https://api.zivlo.com.br
ℹ️ Todos os endpoints aceitam e retornam JSON. Inclua o header Content-Type: application/json em todas as requisições.

🔐 Autenticação

A API usa tokens Bearer para autenticação. Inclua o token no header de cada requisição:

HTTP Header
Authorization: Bearer whsp_sua_chave_aqui

Alternativamente, use o header X-API-Token:

HTTP Header (alternativo)
X-API-Token: whsp_sua_chave_aqui
⚠️ Tokens são exibidos uma única vez no momento da criação. Salve-o em um local seguro — o Zivlo armazena apenas o hash do token e não é possível recuperá-lo.

Formato do token

Todos os tokens seguem o padrão:

Formato
whsp_<40 caracteres hexadecimais>

Exemplo: whsp_a3f8d2e1c9b74f620a8e5d3c1b9f7e4a2d6c8b5

⚡ Respostas de erro

Todos os erros seguem o mesmo formato JSON:

JSON
{
  "error": "Descrição do erro"
}

Códigos HTTP

200Sucesso — operação realizada com êxito
400Bad Request — campo obrigatório ausente ou inválido
401Unauthorized — token ausente, inválido ou expirado
403Forbidden — conta banida ou acesso negado
404Not Found — conta, fluxo ou recurso não encontrado
409Conflict — já existe uma sessão ativa para este número
422Unprocessable — conta Cloud API sem configuração completa
429Too Many Requests — limite de requisições excedido
503Service Unavailable — conta Baileys desconectada
500Internal Server Error — erro inesperado no servidor

🚦 Rate Limit

Os endpoints públicos /v1/* não possuem rate limiting por padrão. O rate limit de 100 req/min aplica-se apenas às rotas internas do painel.

Para ambientes de produção, recomendamos implementar throttling no seu lado para evitar sobrecarga da fila de mensagens.

💡 O envio de mensagens via Baileys é assíncrono — a API confirma o enfileiramento, não a entrega. Use os webhooks do WhatsApp Cloud API para rastrear status de entrega.

📨 Enviar mensagem

Envia uma mensagem WhatsApp (texto, mídia ou template) via uma conta conectada.

POST /v1/messages/send
CampoTipoReq.Descrição
account_iduuidSimUUID da conta WhatsApp conectada no Zivlo
tostringSimNúmero do destinatário com DDI. Apenas dígitos (ex: 5511999998888)
typestringSimTipo: text · image · video · audio · document · template
messaging_productstringSimSempre "whatsapp"
text.bodystringCond.Corpo da mensagem quando type = "text"
image.linkstringCond.URL pública da imagem quando type = "image"
image.captionstringNãoLegenda da imagem (opcional)
video.linkstringCond.URL pública do vídeo quando type = "video"
audio.linkstringCond.URL pública do áudio quando type = "audio"
document.linkstringCond.URL pública do documento quando type = "document"
document.filenamestringNãoNome do arquivo a exibir no WhatsApp
template.namestringCond.Nome do template aprovado pela Meta (apenas Cloud API)
template.language.codestringCond.Ex: "pt_BR" — código de idioma do template
template.componentsarrayNãoVariáveis de preenchimento do template (header, body, buttons)
ℹ️ Envio de templates é suportado apenas em contas WhatsApp Cloud API. Contas Baileys suportam texto e mídia (image, video, audio, document).

Mensagem de texto

bash
curl -X POST https://api.zivlo.com.br/v1/messages/send \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "messaging_product": "whatsapp",
    "to": "5511999998888",
    "type": "text",
    "text": { "body": "Olá! Sua consulta foi confirmada." }
  }'

Imagem com legenda

bash
curl -X POST https://api.zivlo.com.br/v1/messages/send \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "messaging_product": "whatsapp",
    "to": "5511999998888",
    "type": "image",
    "image": {
      "link": "https://exemplo.com/imagem.jpg",
      "caption": "Veja nossa novidade!"
    }
  }'

Template (Cloud API)

bash
curl -X POST https://api.zivlo.com.br/v1/messages/send \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "messaging_product": "whatsapp",
    "to": "5511999998888",
    "type": "template",
    "template": {
      "name": "confirmacao_consulta",
      "language": { "code": "pt_BR" },
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "João Silva" },
            { "type": "text", "text": "20/03/2026 às 14h" }
          ]
        }
      ]
    }
  }'
javascript
const response = await fetch('https://api.zivlo.com.br/v1/messages/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer whsp_sua_chave',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    account_id: 'uuid-da-conta',
    messaging_product: 'whatsapp',
    to: '5511999998888',
    type: 'text',
    text: { body: 'Olá! Sua consulta foi confirmada.' },
  }),
});

const data = await response.json();
console.log(data); // { ok: true, wa_message_id: "wamid.xxx" }
python
import requests

url = "https://api.zivlo.com.br/v1/messages/send"

headers = {
    "Authorization": "Bearer whsp_sua_chave",
    "Content-Type": "application/json",
}

payload = {
    "account_id": "uuid-da-conta",
    "messaging_product": "whatsapp",
    "to": "5511999998888",
    "type": "text",
    "text": {"body": "Olá! Sua consulta foi confirmada."},
}

response = requests.post(url, json=payload, headers=headers)
print(response.json())  # {'ok': True, 'wa_message_id': 'wamid.xxx'}

Sucesso (200)

JSON
// Baileys
{ "ok": true }

// Cloud API
{
  "ok": true,
  "wa_message_id": "wamid.HBgNNTUxMTk5OTk5ODg4FQIAERgSM..."
}

Erros possíveis

400account_id must be a valid UUID · "to" must contain only digits · type is required
401Invalid or expired API token
403Account is banned
404Account not found
503Baileys account not connected
Testar requisição
200 OK

        

⚡ Disparar fluxo

Inicia uma sessão de automação para um contato, como se ele tivesse enviado a mensagem gatilho. Ideal para campanhas, agendamentos e integrações com outros sistemas.

POST /v1/flows/trigger
CampoTipoReq.Descrição
account_iduuidSimUUID da conta WhatsApp que enviará as mensagens do fluxo
flow_iduuidSimUUID do fluxo a ser executado. O fluxo deve estar ativo (is_active = true)
tostringNão*Número do destinatário com DDI. Se omitido, usa variables.phone
variablesobjectNãoVariáveis iniciais injetadas no fluxo. Mescladas com phone, date e time
forcebooleanNãoSe true, cancela sessão ativa existente e reinicia o fluxo. Padrão: false

* to ou variables.phone são obrigatórios.

Variáveis automáticas

As seguintes variáveis são sempre injetadas na sessão, independente do que você enviar:

VariávelExemploDescrição
{{phone}}5511999998888Número do destinatário
{{date}}20/03/2026Data atual (formato pt-BR)
{{time}}14:32:08Hora atual (formato pt-BR)
⚠️ Se já existir uma sessão ativa para o número, a API retorna 409 Conflict. Use force: true para cancelar a sessão existente e iniciar uma nova.

Disparar fluxo simples

bash
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "flow_id": "uuid-do-fluxo",
    "to": "5511999998888"
  }'

Com variáveis personalizadas

bash
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "flow_id": "uuid-do-fluxo",
    "to": "5511999998888",
    "variables": {
      "nome_paciente": "João Silva",
      "data_consulta": "20/03/2026",
      "horario": "14h30",
      "medico": "Dr. Carlos"
    }
  }'

Forçar reinício de sessão

bash
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "uuid-da-conta",
    "flow_id": "uuid-do-fluxo",
    "to": "5511999998888",
    "force": true
  }'
javascript
// Disparar fluxo com variáveis de agendamento
async function triggerAppointmentFlow(phone, appointment) {
  const response = await fetch('https://api.zivlo.com.br/v1/flows/trigger', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      account_id: process.env.ZIVLO_ACCOUNT_ID,
      flow_id: 'uuid-do-fluxo-consulta',
      to: phone,
      variables: {
        nome_paciente: appointment.patientName,
        data_consulta: appointment.date,
        horario: appointment.time,
        medico: appointment.doctor,
      },
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error);
  }

  return await response.json();
  // { ok: true, session_id: "uuid-da-sessao" }
}

// Uso com webhook de agendamento
app.post('/webhook/agendamento', async (req, res) => {
  const { phone, ...appointment } = req.body;
  try {
    const result = await triggerAppointmentFlow(phone, appointment);
    res.json({ success: true, session_id: result.session_id });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});
python
import requests
import os

def trigger_flow(phone: str, flow_id: str, variables: dict = None, force: bool = False):
    """Dispara um fluxo Zivlo para um número de WhatsApp."""
    url = "https://api.zivlo.com.br/v1/flows/trigger"

    headers = {
        "Authorization": f"Bearer {os.environ['ZIVLO_API_KEY']}",
        "Content-Type": "application/json",
    }

    payload = {
        "account_id": os.environ["ZIVLO_ACCOUNT_ID"],
        "flow_id": flow_id,
        "to": phone,
        "force": force,
    }

    if variables:
        payload["variables"] = variables

    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()
    return response.json()


# Exemplo: Notificar paciente de consulta
result = trigger_flow(
    phone="5511999998888",
    flow_id="uuid-fluxo-consulta",
    variables={
        "nome_paciente": "João Silva",
        "data_consulta": "20/03/2026",
        "horario": "14h30",
    }
)
print(result)  # {'ok': True, 'session_id': 'uuid-da-sessao'}

Sucesso (200)

JSON
{
  "ok": true,
  "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Erros possíveis

400account_id must be a valid UUID · flow_id must be a valid UUID
401Invalid or expired API token
404Account not found · Flow not found or not active · Flow does not belong to your tenant
409Phone already has an active flow session. Pass "force": true
503Account not connected (Baileys desconectado)
Testar requisição
200 OK

        

📒 Criar lista de contatos

Cria uma nova lista de contatos ou retorna a lista existente (se já houver uma com o mesmo nome no seu tenant). Útil antes de importar contatos com /v1/contacts/import.

POST /v1/contacts/lists
CampoTipoReq.Descrição
namestringSimNome da lista. Único por tenant (máx 200 caracteres)
descriptionstringNãoDescrição opcional (máx 2000 caracteres)
ℹ️ Se já existir uma lista com esse nome, a resposta traz created: false e devolve a lista existente (status 200). Quando a lista é criada do zero, o status é 201 Created.
bash
curl -X POST https://api.zivlo.com.br/v1/contacts/lists \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Clientes Black Friday",
    "description": "Campanha de novembro 2026"
  }'
javascript
const res = await fetch('https://api.zivlo.com.br/v1/contacts/lists', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Clientes Black Friday',
    description: 'Campanha de novembro 2026',
  }),
});

const { list, created } = await res.json();
console.log('List ID:', list.id, '(new:', created, ')');
python
import os, requests

res = requests.post(
    "https://api.zivlo.com.br/v1/contacts/lists",
    headers={
        "Authorization": f"Bearer {os.environ['ZIVLO_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "name": "Clientes Black Friday",
        "description": "Campanha de novembro 2026",
    },
)
res.raise_for_status()
list_id = res.json()["list"]["id"]
print("List ID:", list_id)

Sucesso (201 — criada) / (200 — existente)

JSON
{
  "ok": true,
  "created": true,
  "list": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "Clientes Black Friday",
    "description": "Campanha de novembro 2026",
    "contact_count": 0,
    "created_at": "2026-04-24T12:34:56Z"
  }
}

Erros possíveis

400name is required and must be a non-empty string
401Invalid or revoked API token

📥 Importar contatos

Importa contatos em massa para uma lista. Números são normalizados (apenas dígitos) antes da gravação. Aceita até 1000 contatos por requisição e faz upsert por (list_id, phone) — a mesma chamada pode criar e atualizar ao mesmo tempo.

POST /v1/contacts/import

Campos da requisição

CampoTipoReq.Descrição
list_iduuidCond.*UUID de uma lista existente. Use este OU list_name
list_namestringCond.*Nome da lista. Cria automaticamente se não existir (controlado por create_list_if_missing)
create_list_if_missingbooleanNãoPadrão true. Quando false, retorna 404 se a lista nomeada não existir
upsertbooleanNãoPadrão true. Quando false, contatos já existentes são pulados (não atualizados)
contactsarraySimLista de contatos a importar (1 até 1000 itens)

* Informe um entre list_id ou list_name.

Campos de cada contato

CampoTipoReq.Descrição
phonestringSimTelefone com DDI. Aceita qualquer formato — tudo que não é dígito é removido. Mínimo 8 dígitos
namestringNãoNome de exibição (máx 200)
emailstringNãoE-mail (máx 320)
tagsstring[]NãoAté 20 tags, cada uma com até 64 caracteres
notesstringNãoObservações internas (máx 2000)
birth_datestringNãoData de nascimento no formato YYYY-MM-DD
avatar_urlstringNãoURL pública da foto de perfil
custom_fieldsobjectNãoObjeto plano com campos customizados (ex: { "cidade": "SP", "cpf": "..." }). Ficam disponíveis como variáveis de fluxo
ℹ️ O telefone é normalizado antes da gravação: "+55 (11) 99999-8888" → "5511999988888". Duplicatas dentro da mesma requisição são reportadas em errors.
⚠️ Um contato inválido (ex: telefone com menos de 8 dígitos) não aborta a importação — ele vai para errors e os demais são processados normalmente.

Importação básica

bash
curl -X POST https://api.zivlo.com.br/v1/contacts/import \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "list_name": "Clientes Black Friday",
    "contacts": [
      { "phone": "+55 11 99999-8888", "name": "João Silva", "email": "joao@example.com" },
      { "phone": "5511999997777", "name": "Maria Souza", "tags": ["vip", "whatsapp-ads"] }
    ]
  }'

Com campos customizados e upsert desativado

bash
curl -X POST https://api.zivlo.com.br/v1/contacts/import \
  -H "Authorization: Bearer whsp_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "list_id": "uuid-da-lista",
    "upsert": false,
    "contacts": [
      {
        "phone": "5511999998888",
        "name": "João Silva",
        "birth_date": "1990-05-20",
        "custom_fields": {
          "cidade": "São Paulo",
          "produto_interesse": "plano-anual"
        }
      }
    ]
  }'
javascript
// Importa contatos vindos de um CSV / export de CRM
async function importContacts(listName, contacts) {
  const res = await fetch('https://api.zivlo.com.br/v1/contacts/import', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      list_name: listName,
      upsert: true,
      contacts,
    }),
  });

  if (!res.ok) {
    const err = await res.json();
    throw new Error(err.error);
  }

  return await res.json();
}

// Dica: divida em lotes de 500 para listas muito grandes
function chunk(arr, size = 500) {
  const out = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

const allContacts = [
  { phone: '+55 11 99999-8888', name: 'João Silva', tags: ['vip'] },
  { phone: '5511999997777', name: 'Maria Souza' },
  // ... N contatos
];

for (const batch of chunk(allContacts, 500)) {
  const result = await importContacts('Clientes Black Friday', batch);
  console.log(result); // { inserted, updated, skipped, failed, errors }
}
python
import csv, os, requests
from itertools import islice

URL   = "https://api.zivlo.com.br/v1/contacts/import"
TOKEN = os.environ["ZIVLO_API_KEY"]

def chunks(iterable, size=500):
    it = iter(iterable)
    while batch := list(islice(it, size)):
        yield batch

def import_contacts(list_name, contacts):
    res = requests.post(
        URL,
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={"list_name": list_name, "upsert": True, "contacts": contacts},
    )
    res.raise_for_status()
    return res.json()

# Carrega um CSV com colunas: phone, name, email, cidade
with open("contacts.csv") as f:
    rows = [
        {
            "phone": r["phone"],
            "name": r["name"],
            "email": r["email"],
            "custom_fields": {"cidade": r["cidade"]},
        }
        for r in csv.DictReader(f)
    ]

for batch in chunks(rows, 500):
    result = import_contacts("Clientes Black Friday", batch)
    print(result)  # {'inserted': 420, 'updated': 60, 'skipped': 0, 'failed': 20, 'errors': [...]}

Sucesso (200)

JSON
{
  "ok": true,
  "list_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "received": 500,
  "inserted": 420,
  "updated": 60,
  "skipped": 0,
  "failed": 20,
  "errors": [
    { "index": 17, "phone": "+1 555",
      "error": "phone has fewer than 8 digits after normalization" },
    { "index": 92, "phone": "5511999998888",
      "error": "duplicate phone in the same request" }
  ]
}

Campos da resposta

CampoTipoDescrição
list_iduuidUUID da lista (criada ou reutilizada)
receivedintegerTotal de itens enviados no contacts
insertedintegerNovos contatos criados
updatedintegerContatos já existentes que foram atualizados (apenas quando upsert: true)
skippedintegerContatos pulados por já existirem (apenas quando upsert: false)
failedintegerContatos inválidos ou não gravados
errorsarrayDetalhes de cada falha: { index, phone?, error }

Erros possíveis

400contacts must be an array · Too many contacts in a single request (max 1000) · Either list_id or list_name is required · No valid contacts to import
401Invalid or revoked API token
404List not found or does not belong to your tenant
500Upsert failed: ... · Insert failed: ...

🗝️ Como gerar um token de API

Tokens de API estão disponíveis para planos Pro e superiores.

1
Acesse Configurações → Tokens de API
No painel do Zivlo, clique no ícone de configurações na barra lateral e selecione a aba Tokens de API.
2
Clique em "Novo token"
Dê um nome descritivo ao token (ex: Integração CRM, Webhook Calendly) e selecione o tempo de expiração: nunca, 30 dias, 90 dias ou 1 ano.
3
Copie o token imediatamente
O token completo é exibido uma única vez. Copie e salve em um gerenciador de senhas ou variável de ambiente. O Zivlo armazena apenas o hash — não é possível recuperar o valor original.
4
Use o token nas requisições
Inclua no header Authorization: Bearer whsp_... de todas as chamadas à API.
5
Revogue quando necessário
Para revogar um token, clique no ícone de lixeira ao lado do token na lista. Isso invalida imediatamente todas as requisições que usam aquele token.
🔒 Nunca exponha tokens de API em código frontend, repositórios públicos ou logs. Use variáveis de ambiente (ZIVLO_API_KEY) sempre que possível.

📎 Tipos de mensagem

Compatibilidade por tipo de conta

TipoBaileysCloud APIObservação
text✓✓Suporta emojis e markdown WhatsApp (*bold*, _italic_)
image✓✓JPEG, PNG, WebP. Máx 5 MB (Cloud API) / 16 MB (Baileys)
video✓✓MP4 com codec H.264. Máx 16 MB
audio✓✓MP3, OGG, AAC. Enviado como áudio de voz no Cloud API
document✓✓PDF, DOCX, XLSX, etc. Inclua filename para exibir nome
template✗✓Requer template aprovado pela Meta. Necessário para iniciar conversas
💡 Para iniciar uma conversa com um usuário que nunca entrou em contato (janela de 24h), use templates via Cloud API. Para contas Baileys, o número deve ter enviado uma mensagem nas últimas 24h ou estar salvo nos contatos.

🧩 Variáveis de fluxo

Ao disparar um fluxo via API, você pode injetar variáveis que ficam disponíveis em todos os nós usando a sintaxe {{nome_variavel}}.

Exemplo: confirmação de consulta

Nó "Enviar Mensagem" no editor
Olá, {{nome_paciente}}! 👋

Sua consulta com {{medico}} está confirmada:
📅 Data: {{data_consulta}}
🕐 Horário: {{horario}}

Responda 1 para confirmar ou 2 para cancelar.
Chamada de API correspondente
POST /v1/flows/trigger
{
  "account_id": "...",
  "flow_id": "uuid-do-fluxo-consulta",
  "to": "5511999998888",
  "variables": {
    "nome_paciente": "João Silva",
    "medico": "Dr. Carlos Alves",
    "data_consulta": "20/03/2026",
    "horario": "14h30"
  }
}
💡 Todos os valores de variáveis são convertidos para string automaticamente. Datas, números e booleanos são aceitos e convertidos.

Variáveis capturadas por nós "Fazer Pergunta" e "Agente IA" durante a sessão também ficam disponíveis nos nós seguintes usando a mesma sintaxe.