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
textSuporta emojis e markdown WhatsApp (*bold*, _italic_)
imageJPEG, PNG, WebP. Máx 5 MB (Cloud API) / 16 MB (Baileys)
videoMP4 com codec H.264. Máx 16 MB
audioMP3, OGG, AAC. Enviado como áudio de voz no Cloud API
documentPDF, DOCX, XLSX, etc. Inclua filename para exibir nome
templateRequer 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.