Referência da API /v1

Todos os endpoints REST da BotoZap, agrupados por recurso, com o escopo exigido, o modelo de paginação e as honestidades que valem a pena saber antes de integrar.

Esta é a lista completa das rotas /v1: 134 pares de método e caminho nas tabelas abaixo, mais as 153 rotas do Agente de IA, listadas no guia próprio. As tabelas saem de um inventário verificado contra o código das rotas a cada mudança: rota nova, escopo trocado ou modelo de paginação diferente quebram o teste antes de chegar aqui.

O contrato exato de cada request e response está nos exemplos testados das páginas de guia (Enviar mensagens, Webhooks, Transmissões e limites, entre outras). Cada exemplo roda contra o handler real antes de ser publicado.

Convenções

Base URL de todas as rotas:

https://botozap.com.br/api/v1

Toda requisição leva a sua chave de API no header, como X-API-Key ou como Authorization: Bearer. Se os dois vierem, vale o X-API-Key. A conta é derivada da chave, então você nunca passa um id de conta (o isolamento multi-tenant é garantido no servidor). Detalhes em Autenticação.

X-API-Key: $BOTOZAP_KEY
# ou
Authorization: Bearer $BOTOZAP_KEY

As respostas seguem um envelope fixo:

  • Sucesso singular: { "data": <objeto> }.
  • Lista por cursor: { "data": [...], "paging": {...} }; lista por página: { "data": [...], "meta": {...} }. Algumas listas curtas (etapas, campos, calendários de uma conexão) vêm inteiras, só com data.
  • DELETE bem-sucedido costuma responder 204 sem corpo.
  • Exceção: POST /api/v1/messages responde direto, sem data: { id, wamid, to, sent_to, status }, mais warnings quando há aviso de consentimento e sandbox: true com chave sandbox.

Erros vêm num envelope estruturado com código e mensagem:

{
  "error": {
    "code": "forbidden_scope",
    "message": "Esta chave de API não tem o escopo 'messages:send'."
  }
}

Os códigos que qualquer rota pode devolver (chave inválida, escopo, sandbox, rate limit, conta em exclusão, acesso ao produto) e o que fazer em cada caso estão em Erros.

Escopos

Cada rota exige um escopo específico (coluna Escopo nas tabelas abaixo). A chave só acessa as rotas cujos escopos ela tem; lista vazia não libera nada. O catálogo completo, agrupado por área, está em Erros e escopos.

Ambiente sandbox

Uma chave bz_sandbox_ só acessa uma allow-list fail-closed de rotas (marcadas "Aceita chave sandbox" nas tabelas). Qualquer outra rota responde 403 sandbox_forbidden. Hoje a lista tem 44 rotas: POST /api/v1/messages, GET /api/v1/messages, GET /api/v1/messages/{id}, GET /api/v1/events, GET /api/v1/templates, POST /api/v1/templates, GET /api/v1/templates/{id}, GET /api/v1/contacts, GET /api/v1/conversations, GET /api/v1/conversations/{id}, PATCH /api/v1/conversations/{id}, GET /api/v1/conversations/{id}/assignments, POST /api/v1/conversations/{id}/assignments, GET /api/v1/conversations/{id}/assignments/{assignmentId}, PATCH /api/v1/conversations/{id}/assignments/{assignmentId}, GET /api/v1/conversations/{id}/tools, PATCH /api/v1/conversations/{id}/tools, GET /api/v1/opportunities, POST /api/v1/opportunities, GET /api/v1/opportunities/{id}, PATCH /api/v1/opportunities/{id}, GET /api/v1/opportunities/{id}/activities, GET /api/v1/opportunities/{id}/conversations, POST /api/v1/opportunities/{id}/conversations, DELETE /api/v1/opportunities/{id}/conversations, GET /api/v1/demands, POST /api/v1/demands, GET /api/v1/demands/{id}, PATCH /api/v1/demands/{id}, GET /api/v1/demands/{id}/activities, GET /api/v1/demands/{id}/conversations, POST /api/v1/demands/{id}/conversations, DELETE /api/v1/demands/{id}/conversations, GET /api/v1/radar, GET /api/v1/radar/stage-rules, GET /api/v1/channel_accounts, GET /api/v1/channel_accounts/{id}, GET /api/v1/phone_numbers, GET /api/v1/channel_accounts/{id}/comment-rules, POST /api/v1/channel_accounts/{id}/comment-rules, GET /api/v1/channel_accounts/{id}/comment-rules/{ruleId}, PATCH /api/v1/channel_accounts/{id}/comment-rules/{ruleId}, GET /api/v1/media/{id} e GET /api/v1/media/{id}/download.

Nessas rotas a chave sandbox só enxerga o ambiente dela: números, Contas de canal, contatos, conversas, templates e Eventos sintéticos. O envio aceita qualquer destino e nunca chama a Meta; os números mágicos escolhem o cenário simulado. POST /api/v1/templates cria o template já APPROVED no catálogo sintético. Nas rotas de mídia, só a mídia sintética sandbox-media-0001 responde. No CRM, contato e conversa precisam ser do mesmo ambiente da chave. Veja Sandbox vs. produção.

Paginação

Três modelos coexistem, dependendo do recurso, nunca dois na mesma rota. O rótulo "Paginado por…" em cada linha diz qual vale.

Por cursor

Para coleções grandes e de append. Query params: limit (padrão 20, máximo 100), after e before. A resposta traz:

{
  "data": [...],
  "paging": {
    "cursors": { "before": "...", "after": "..." },
    "next": "...",
    "previous": "..."
  }
}

A lista vem do mais novo para o mais antigo. after traz os limit itens imediatamente mais antigos que o cursor (use paging.next); before traz os limit itens imediatamente mais novos que ele, na mesma ordem (use paging.previous).

Rotas por cursor: GET /api/v1/messages, GET /api/v1/broadcasts/{id}/recipients, GET /api/v1/contacts, GET /api/v1/conversations, GET /api/v1/journeys, GET /api/v1/journeys/{id}/runs, GET /api/v1/webhooks, GET /api/v1/webhook_deliveries e GET /api/v1/api_logs.

Por página (offset)

Para coleções pequenas e estáveis. Query params: page e per_page (padrão 20, máximo 100). A resposta traz:

{
  "data": [...],
  "meta": { "page": 1, "per_page": 20, "total_pages": 3, "total_count": 42 }
}

Os mesmos números também vêm nos headers X-Page, X-Per-Page, X-Total-Pages e X-Total. Rotas por página: GET /api/v1/customers, GET /api/v1/customers/{id}/setup_links, GET /api/v1/templates, GET /api/v1/broadcasts, GET /api/v1/conversations/{id}/assignments, GET /api/v1/saved-replies, GET /api/v1/opportunities, GET /api/v1/opportunities/{id}/activities, GET /api/v1/opportunities/{id}/conversations, GET /api/v1/demands, GET /api/v1/demands/{id}/activities, GET /api/v1/demands/{id}/conversations, GET /api/v1/radar, GET /api/v1/appointments, GET /api/v1/appointments/{id}/history, GET /api/v1/appointments/services, GET /api/v1/appointments/schedules, GET /api/v1/appointments/exceptions, GET /api/v1/calendar/connections, GET /api/v1/calendar/jobs, GET /api/v1/channel_accounts, GET /api/v1/phone_numbers, GET /api/v1/channel_accounts/{id}/comment-rules e GET /api/v1/users.

Cursor de Eventos

Só GET /api/v1/events. A leitura é ascendente e o cursor é um inteiro contíguo por Conta e ambiente: after (padrão 0, exclusivo) e limit (mesmos padrão e máximo do cursor acima). Guarde paging.cursor depois de processar a página e use-o como after na próxima; repetir a mesma chamada devolve a mesma página. Cursor que não é inteiro não negativo responde 422 invalid_cursor.

{
  "data": [{ "id": "...", "cursor": "42", "type": "...", ... }],
  "paging": { "cursor": "42", "next": null, "has_more": false }
}

Mensagens

POST /api/v1/messages envia texto, template ou mídia por link público https (image, video, audio, document, no formato { "link": "https://..." }). Mídia por id não é aceita no envio: responde 422 invalid_request. O media_id de POST /api/v1/media só serve dentro de template.components, no cabeçalho do template.

A resposta 201 traz to (o que você endereçou) e sent_to (o endereço que de fato foi à Meta, depois da normalização). Em template de marketing para contato sem Consentimento registrado (e sem consent_basis no corpo), o envio segue e a resposta traz warnings: [{ code: "consent_unspecified", ... }]; Consentimento revogado recusa com 422 consent_revoked. Em recusas, o erro ganha outcome, retry e next_step (e meta_code quando a Meta respondeu), ver Erros de envio.

MétodoCaminhoEscopoDescrição
POST/api/v1/messagesmessages:sendEnvia texto, template, mídia por link (image, video, audio, document), interactive (botões, lista, botão de link) ou location. Responde 201 com id, wamid, to, sent_to, status e, quando houver, warnings e sandbox: true. Aceita chave sandbox.
GET/api/v1/messagesmessages:readLista as mensagens da conta. Filtros de canal: channel e channel_account_id. Paginado por cursor. Aceita chave sandbox.
GET/api/v1/messages/{id}messages:readConsulta uma mensagem por UUID interno ou wamid. Aceita chave sandbox.

Contrato detalhado e exemplos em Enviar mensagens.

Eventos

O log durável de Eventos da Conta, o mesmo que alimenta os webhooks, lido em ordem por cursor. Serve para reconciliar depois de uma queda do seu endpoint sem depender de reentrega. Cada ambiente tem o próprio cursor: chave sandbox lê os Eventos sandbox; chave live, os de produção.

MétodoCaminhoEscopoDescrição
GET/api/v1/eventsevents:readReplay ascendente do log durável de Eventos da Conta, separado por ambiente da chave. after é o último cursor já processado (exclusivo). Paginado pelo cursor de Eventos. Aceita chave sandbox.

Contrato detalhado e exemplos em Eventos.

Clientes e links de setup

Cada Cliente traz is_self. Com true, é o Cliente que representa o negócio da própria Conta, criado no cadastro (no máximo um por Conta). O campo é só leitura, e nenhuma rota o trata de forma especial; use-o para esconder ou rotular essa camada na sua interface. O DELETE responde 409 customer_in_use enquanto houver conexões ou links de setup vinculados.

O POST de links de setup aceita allowed_connection_types (dedicated e/ou coexistence; padrão dedicated), success_redirect_url e failure_redirect_url. Uma lista sem nenhum tipo válido responde 422 invalid_connection_types (tipos desconhecidos ao lado de válidos são ignorados); URL de redirecionamento que não seja https, que traga usuário ou senha ou que passe de 2048 caracteres responde 422 invalid_redirect_url. A resposta traz a url a enviar ao Cliente, status, whatsapp_setup_status (pending, in_progress, completed ou failed), theme_config (reservado: sempre null, a página de conexão não aplica tema) e expires_at. A página de conexão é sempre em português: language continua aceito por compatibilidade, mas é ignorado (links novos voltam com language: null).

O campo provision_phone_number foi removido em setembro de 2026: o BotoZap não oferece provisionamento de número, e o Cliente conecta o número dele pelo cadastro da Meta. As respostas não trazem mais o campo; se uma integração ainda o enviar, ele é ignorado como qualquer chave desconhecida, sem erro.

Os redirecionamentos acontecem na página de conexão, só em estado final do link. Quando a conexão conclui, a página mostra "Voltando para <host>…" e leva o Cliente para success_redirect_url com status=completed. Confirme se o número está pronto em GET /phone_numbers, porque uma conexão concluída ainda pode ter pendência de registro. Se o link se esgota sem concluir, o destino é failure_redirect_url com status=failed. Nos erros em que dá para tentar de novo, a página oferece "Voltar para <host>", que leva a failure_redirect_url com status=cancelled, e o link continua válido. Todo destino recebe também setup_link_id; os parâmetros que a sua URL já tiver são preservados, e o token do link nunca é enviado. Link expirado ou revogado mostra a página de erro, sem redirecionar.

MétodoCaminhoEscopoDescrição
GET/api/v1/customerscustomers:readLista os seus Clientes. Cada item traz is_self. Paginado por página.
POST/api/v1/customerscustomers:writeCria um Cliente (name, external_customer_id opcional e único na Conta; repetido responde 409 external_id_conflict).
GET/api/v1/customers/{id}customers:readConsulta um Cliente.
PATCH/api/v1/customers/{id}customers:writeAtualiza name e/ou external_customer_id (vazio limpa). is_self é só leitura.
DELETE/api/v1/customers/{id}customers:writeRemove um Cliente sem conexões nem links de setup vinculados; com vínculos responde 409 customer_in_use. O Cliente da própria empresa (is_self: true) não pode ser excluído: 409 self_customer.
GET/api/v1/customers/{id}/setup_linkscustomers:readLista os links de onboarding do Cliente. Paginado por página.
POST/api/v1/customers/{id}/setup_linkscustomers:writeCria um link de onboarding (Embedded Signup) válido por 30 dias. A página do link é sempre em português: language é aceito por compatibilidade e ignorado.
PATCH/api/v1/customers/{id}/setup_links/{linkId}customers:writeAtualiza status (active, expired ou revoked) ou expires_at, inclusive para revogar. Não há GET nem DELETE de um link específico.

Contrato detalhado e exemplos em Quickstart: revenda.

Templates

O POST leva name (minúsculas, números e underscore), language (pt_BR), category (UTILITY, MARKETING ou AUTHENTICATION) e components no formato da Cloud API, além de waba_connection_id ou phone_number_id (os dois saem de GET /phone_numbers). Sem conexão: 422 missing_connection. Payload local inválido: 422 invalid_template. Recusa da Meta: 422 ou 502 meta_error. Em live o 201 volta PENDING; o envio só vale com status APPROVED. Hoje não existe PATCH nem DELETE de template pela API; para editar ou remover, use o painel.

MétodoCaminhoEscopoDescrição
GET/api/v1/templatestemplates:readLista os templates da conta. Com chave sandbox, só os do catálogo sintético. Paginado por página. Aceita chave sandbox.
POST/api/v1/templatestemplates:writeCria um template e o submete à Meta. Exige waba_connection_id ou phone_number_id. Nome e idioma já no catálogo da conexão: 409 duplicate, sem alterar o existente. Com chave sandbox, nasce APPROVED no catálogo sintético, sem chamar a Meta. Aceita chave sandbox.
GET/api/v1/templates/{id}templates:readConsulta um template. Com chave sandbox, só os do catálogo sintético; rejection_reason traz o motivo da Meta quando REJECTED. Aceita chave sandbox.

Contrato detalhado e exemplos em Templates.

Transmissões

Limites de destinatários por transmissão, quota mensal, rate limit e o messaging limit da Meta são quatro coisas diferentes, detalhadas no guia abaixo.

MétodoCaminhoEscopoDescrição
GET/api/v1/broadcastsbroadcasts:readLista as transmissões. Paginado por página.
POST/api/v1/broadcastsbroadcasts:writeCria uma transmissão em rascunho.
GET/api/v1/broadcasts/{id}broadcasts:readConsulta uma transmissão, seus contadores de progresso e blocked_reason (por que uma transmissão ativa não avança).
PATCH/api/v1/broadcasts/{id}broadcasts:writeTransição de estado, não edição de conteúdo: { "status": "stopped" } interrompe uma transmissão sending, scheduled ou warming_up; { "status": "draft" } devolve uma scheduled para rascunho. warming_up só aceita stopped.
GET/api/v1/broadcasts/{id}/recipientsbroadcasts:readLista os destinatários. Paginado por cursor.
POST/api/v1/broadcasts/{id}/recipientsbroadcasts:writeAdiciona destinatários (só em rascunho).
DELETE/api/v1/broadcasts/{id}/recipientsbroadcasts:writeRemove todos os destinatários (só em rascunho).
POST/api/v1/broadcasts/{id}/schedulebroadcasts:writeAgenda o envio.
POST/api/v1/broadcasts/{id}/sendbroadcasts:writeDispara o envio (assíncrono, responde 202).
POST/api/v1/broadcasts/{id}/cancelbroadcasts:writeCancela uma transmissão agendada ou em rascunho. Não interrompe envio em curso nem warming_up; para isso use o PATCH.

Contrato detalhado e exemplos em Transmissões e limites.

Contatos, etapas e campos

Atenção: DELETE /api/v1/contacts/{id} é síncrono e definitivo. Ele apaga, na mesma chamada, as mensagens, as conversas e os registros de mídia do contato antes do próprio contato.

MétodoCaminhoEscopoDescrição
GET/api/v1/contactscontacts:readLista os contatos. Filtros: metadata[chave]=valor, channel e channel_account_id. Paginado por cursor. Aceita chave sandbox.
POST/api/v1/contactscontacts:writeCria um contato. Aceita display_name (nome dado pela sua empresa), metadata e channel_account_id como alternativa a phone_number_id.
GET/api/v1/contacts/{id}contacts:readConsulta um contato.
PATCH/api/v1/contacts/{id}contacts:writeAtualiza profile_name, display_name (null limpa), username, notes, stage_id (null limpa) e metadata (merge raso; null remove a chave).
DELETE/api/v1/contacts/{id}contacts:writeApaga o contato na hora, junto com as mensagens, as conversas, os registros de mídia e os eventos do stream dele. Tudo ou nada: em erro, nada é apagado e dá para repetir. Não há como desfazer.
GET/api/v1/contacts/{id}/consentscontacts:readLista o Consentimento vigente e o histórico do Contato.
POST/api/v1/contacts/{id}/consentscontacts:writeGrava Consentimento (canal, Finalidade, estado, evidência; origem api).
GET/api/v1/contact-stagescontacts:readLista as Etapas do Funil do Cliente (customer_id), ordenadas por posição.
POST/api/v1/contact-stagescontacts:writeCria uma Etapa custom (customer_id, label e color).
PATCH/api/v1/contact-stages/{id}contacts:writeRenomeia ou recolore uma Etapa do Cliente (customer_id, label, color).
DELETE/api/v1/contact-stages/{id}contacts:writeExclui uma Etapa custom do Cliente (as de sistema respondem 422). Na mesma transação, os contatos e as oportunidades da etapa ficam sem etapa; cada oportunidade registra a mudança no histórico (crm.opportunity.updated).
PATCH/api/v1/contact-stages/reordercontacts:writeReordena atomicamente todas as Etapas do Cliente (customer_id + stage_ids).
GET/api/v1/contact-fieldscontacts:readLista os campos personalizados da conta, ordenados por posição.
POST/api/v1/contact-fieldscontacts:writeCria um campo personalizado (label; key e type opcionais, type padrão text).
PATCH/api/v1/contact-fields/{id}contacts:writeRenomeia ou reordena um campo (label, position; key e type são imutáveis).
DELETE/api/v1/contact-fields/{id}contacts:writeExclui a definição do campo (não apaga o dado já gravado no metadata).

Contrato detalhado e exemplos em Contatos e funil.

Conversas e atribuições

A listagem aceita filtros combináveis: customer_id (as conversas dos números daquele cliente; um customer_id que não seja da sua conta responde 422 invalid_customer, nunca dados de outro tenant), phone_number_id (id da Meta ou UUID interno do número), status (active ou ended, derivado da janela de 24h) e contact (busca parcial por nome, @username, telefone ou wa_id).

include=last_message embute a última mensagem de cada conversa (null quando ainda não há nenhuma), reduzida a id, wamid, direction, type, status, content, event_at, wa_timestamp e created_at. Exemplo testado em Enviar mensagens.

O PATCH da conversa só aceita { "status": "active" | "ended" }. ended encerra a conversa como o painel (preenche closed_at); active a reabre. A janela de 24h não muda: só uma nova mensagem do contato a reabre. Atribuições apontam para membros de GET /users; só uma fica ativa por conversa.

MétodoCaminhoEscopoDescrição
GET/api/v1/conversationsconversations:readLista as conversas. Filtros: customer_id, phone_number_id, status, contact, channel, channel_account_id e agent_paused=true (só conversas com o agente de IA pausado esperando um humano; outro valor não filtra); expansão include=last_message. Cada conversa traz entry_point (ctwa quando veio de anúncio Click-to-WhatsApp), referral (último clique), fep_expires_at, fep_reply_by e agent_paused_at (ISO ou null). status é ended quando a conversa foi encerrada (closed_at) ou a janela de 24h fechou. Paginado por cursor. Aceita chave sandbox.
GET/api/v1/conversations/{id}conversations:readConsulta uma conversa. Aceita chave sandbox.
PATCH/api/v1/conversations/{id}conversations:writeCorpo { "status": "ended" } encerra a conversa como o botão Encerrar do painel (closed_at); "active" a reabre. Nenhum dos dois mexe na janela de 24h. Mensagem do contato no mesmo instante: 409 conversation_changed. Outro valor: 422 invalid_status. Aceita chave sandbox.
GET/api/v1/conversations/{id}/assignmentsconversations:readLista as atribuições da conversa. Filtro ?active=true|false. Paginado por página. Aceita chave sandbox.
POST/api/v1/conversations/{id}/assignmentsconversations:writeAtribui a um membro (user_id obrigatório, notes opcional). Desativa a atribuição ativa anterior; disputa simultânea responde 409 assignment_conflict. Aceita chave sandbox.
GET/api/v1/conversations/{id}/assignments/{assignmentId}conversations:readConsulta uma atribuição. Aceita chave sandbox.
PATCH/api/v1/conversations/{id}/assignments/{assignmentId}conversations:writeAltera user_id, notes (null limpa) ou active (false desatribui; true reativa e desativa a outra ativa). Membro inválido: 422 invalid_user. Aceita chave sandbox.

Atendimento: respostas compartilhadas e ferramentas do Inbox

A API não expõe respostas pessoais nem rascunhos. Respostas compartilhadas não aceitam chave sandbox; ferramentas do Inbox respeitam o ambiente da conversa.

MétodoCaminhoEscopoDescrição
GET/api/v1/saved-repliessaved_replies:readLista as respostas compartilhadas. Paginado por página.
POST/api/v1/saved-repliessaved_replies:writeCria uma resposta compartilhada; responde 201.
GET/api/v1/saved-replies/{id}saved_replies:readConsulta uma resposta compartilhada.
PATCH/api/v1/saved-replies/{id}saved_replies:writeEdita com expected_updated_at no corpo.
DELETE/api/v1/saved-replies/{id}saved_replies:writeExclui com expected_updated_at no corpo.
GET/api/v1/conversations/{id}/toolsinbox:readEstado, notas e retornos da conversa, com totais separados. Aceita chave sandbox.
PATCH/api/v1/conversations/{id}/toolsinbox:writeNotas, retornos, arquivo e adiamento com controle de versão. Aceita chave sandbox.

Contrato detalhado e exemplos em Atendimento pela API.

CRM: oportunidades, demandas e Radar

Oportunidades, demandas e o Radar fazem parte do funil, disponível nos planos pagos. Sem esse direito, as rotas respondem 403 feature_unavailable. As listagens exigem customer_id.

MétodoCaminhoEscopoDescrição
GET/api/v1/opportunitiescrm:readLista as oportunidades do Cliente (customer_id obrigatório). Filtros: contact_id, owner_user_id (ou unassigned), stage_id (ou none), status e q. Paginado por página. Aceita chave sandbox.
POST/api/v1/opportunitiescrm:writeCria uma oportunidade para um contato do mesmo ambiente da chave; responde 201. Aceita chave sandbox.
GET/api/v1/opportunities/{id}crm:readConsulta uma oportunidade. Aceita chave sandbox.
PATCH/api/v1/opportunities/{id}crm:writeAtualiza uma oportunidade. Aceita chave sandbox.
GET/api/v1/opportunities/{id}/activitiescrm:readLista o histórico de atividades da oportunidade. Paginado por página. Aceita chave sandbox.
GET/api/v1/opportunities/{id}/conversationscrm:readLista as conversas vinculadas. Paginado por página. Aceita chave sandbox.
POST/api/v1/opportunities/{id}/conversationscrm:writeVincula uma conversa (conversation_id no corpo). Aceita chave sandbox.
DELETE/api/v1/opportunities/{id}/conversationscrm:writeDesvincula uma conversa (conversation_id no corpo). Aceita chave sandbox.
GET/api/v1/demandscrm:readLista as demandas do Cliente (customer_id obrigatório), com os mesmos filtros das oportunidades. Paginado por página. Aceita chave sandbox.
POST/api/v1/demandscrm:writeCria uma demanda para um contato do mesmo ambiente da chave; responde 201. Aceita chave sandbox.
GET/api/v1/demands/{id}crm:readConsulta uma demanda. Aceita chave sandbox.
PATCH/api/v1/demands/{id}crm:writeAtualiza uma demanda. Aceita chave sandbox.
GET/api/v1/demands/{id}/activitiescrm:readLista o histórico de atividades da demanda. Paginado por página. Aceita chave sandbox.
GET/api/v1/demands/{id}/conversationscrm:readLista as conversas vinculadas. Paginado por página. Aceita chave sandbox.
POST/api/v1/demands/{id}/conversationscrm:writeVincula uma conversa (conversation_id no corpo). Aceita chave sandbox.
DELETE/api/v1/demands/{id}/conversationscrm:writeDesvincula uma conversa (conversation_id no corpo). Aceita chave sandbox.
GET/api/v1/radarcrm:readPendências do Radar do Cliente (customer_id obrigatório). Filtros: bucket, entity_type, owner_user_id e reason; meta.counts traz os totais. Paginado por página. Aceita chave sandbox.
GET/api/v1/radar/stage-rulescrm:readLista os critérios do Radar por Etapa do Cliente (customer_id). Aceita chave sandbox.
PUT/api/v1/radar/stage-rules/{id}crm:writeConfigura os critérios do Radar de uma Etapa (customer_id e critérios no corpo). Só chave live.

Contrato detalhado e exemplos em CRM pela API.

Agenda

MétodoCaminhoEscopoDescrição
GET/api/v1/appointmentsappointments:readLista compromissos. Filtros: customer_id, contact_id, owner_user_id (ou unassigned), service_id, status, date (dia UTC) ou from/to. Paginado por página.
POST/api/v1/appointmentsappointments:writeCria um compromisso (contact_id e scheduled_at obrigatórios). Aceita o header Idempotency-Key; responde 201.
GET/api/v1/appointments/{id}appointments:readConsulta um compromisso.
PATCH/api/v1/appointments/{id}appointments:writeAtualiza um compromisso; expected_revision opcional protege contra edição concorrente.
DELETE/api/v1/appointments/{id}appointments:writeExclui um compromisso. Se ele está no Google, cancele e aguarde a sincronização antes (409 calendar_sync_pending).
GET/api/v1/appointments/{id}/historyappointments:readHistórico de alterações do compromisso. Paginado por página.
GET/api/v1/appointments/availabilityappointments:readHorários livres entre from e to (até 31 dias) para customer_id, owner_user_id e service_id; exclude_id ignora um compromisso ao remarcar.
GET/api/v1/appointments/servicesappointments:readLista os serviços do Cliente (customer_id). Paginado por página.
POST/api/v1/appointments/servicesappointments:writeCria um serviço; responde 201.
PATCH/api/v1/appointments/services/{id}appointments:writeAtualiza um serviço (exige expected_revision no corpo).
GET/api/v1/appointments/schedulesappointments:readLista as jornadas de atendimento do Cliente (customer_id). Paginado por página.
POST/api/v1/appointments/schedulesappointments:writeCria a jornada da pessoa no Cliente; responde 201. Uma segunda jornada para a mesma pessoa responde 409 schedule_exists.
PATCH/api/v1/appointments/schedules/{id}appointments:writeAtualiza uma jornada (exige expected_revision no corpo).
GET/api/v1/appointments/exceptionsappointments:readLista as exceções de disponibilidade do Cliente (customer_id). Paginado por página.
POST/api/v1/appointments/exceptionsappointments:writeCria uma exceção (available ou busy); responde 201. Exceção idêntica (pessoa, dia, intervalo e tipo) responde 409 exception_exists.
PATCH/api/v1/appointments/exceptions/{id}appointments:writeAtualiza uma exceção (exige expected_revision no corpo).
DELETE/api/v1/appointments/exceptions/{id}appointments:writeRemove uma exceção; exige expected_revision no corpo.

Contrato detalhado e exemplos em Agenda pela API.

Google Calendar

A conta Google é conectada pelo painel (em Configurações › Agenda › Calendários). Pela API você lista conexões, escolhe calendários, acompanha a sincronização e resolve conflitos.

MétodoCaminhoEscopoDescrição
GET/api/v1/calendar/connectionscalendar:readLista as contas Google conectadas à Conta. A conexão em si é feita no painel. Paginado por página.
DELETE/api/v1/calendar/connections/{id}calendar:writeDesconecta uma conta Google.
GET/api/v1/calendar/connections/{id}/calendarscalendar:readLista os calendários da conexão e a seleção de cada um.
POST/api/v1/calendar/connections/{id}/refreshcalendar:writeRelê o catálogo de calendários no Google.
PATCH/api/v1/calendar/selections/{id}calendar:writeDefine destination e include_busy de um calendário (exige expected_revision no corpo).
GET/api/v1/calendar/jobscalendar:readLista os jobs de sincronização. Filtro connection_id. Paginado por página.
POST/api/v1/calendar/jobs/{id}/retrycalendar:writeReenfileira um job de sincronização.
POST/api/v1/calendar/conflicts/{id}/resolvecalendar:writeResolve o conflito de sincronização de um compromisso ({id} do compromisso) escolhendo choice: "local" ou "remote" (exige expected_revision no corpo).

Contrato detalhado e exemplos em Agenda pela API.

Réguas

Réguas e acompanhamentos pertencem ao ambiente da chave que os criou. As alterações usam controle de versão: envie a version atual e trate 409 conflict relendo a Régua.

MétodoCaminhoEscopoDescrição
GET/api/v1/journeysjourneys:readLista as Réguas do ambiente da chave. Filtro customer_id. Paginado por cursor.
POST/api/v1/journeysjourneys:writeCria uma Régua; responde 201 com a definição completa, com steps.
GET/api/v1/journeys/{id}journeys:readConsulta a definição completa de uma Régua.
PUT/api/v1/journeys/{id}journeys:writeSubstitui a definição da Régua; exige a version atual. Sem status, mantém o estado atual. Responde a definição completa, com steps.
DELETE/api/v1/journeys/{id}journeys:writeArquiva a Régua; exige ?version= atual.
POST/api/v1/journeys/{id}/controljourneys:writeAção pause, resume ou archive, com a version atual. resume exige templates APPROVED (422 com template_unavailable).
GET/api/v1/journeys/{id}/runsjourneys:readLista os acompanhamentos (Disparos) da Régua. Paginado por cursor.
POST/api/v1/journeys/{id}/runsjourneys:writeInscreve um contato uma vez por occurrence_key; repetição ou alvo fora da regra ativa responde 409 not_enrolled.
GET/api/v1/journey-runs/{id}journeys:readConsulta um acompanhamento com os passos.
POST/api/v1/journey-runs/{id}/controljourneys:writeAção stop ou acknowledge sobre um acompanhamento (note opcional).

Contrato detalhado e exemplos em Réguas e consentimento.

Webhooks

MétodoCaminhoEscopoDescrição
GET/api/v1/webhookswebhooks:readLista os webhooks com estado sanitizado de saúde; o secret não é retornado. Paginado por cursor.
POST/api/v1/webhookswebhooks:writeRegistra um webhook em unverified; o secret aparece somente no 201.
GET/api/v1/webhooks/{id}webhooks:readConsulta configuração e saúde sanitizada, sem secret.
PATCH/api/v1/webhooks/{id}webhooks:writeAtualiza URL, Authorization, eventos, customer_id, a intenção active ou rotaciona o secret (16–256 caracteres; não é devolvido); URL e Authorization exigem novo teste.
DELETE/api/v1/webhooks/{id}webhooks:writeRemove um webhook.
POST/api/v1/webhooks/{id}/testwebhooks:writeVerifica e ativa com qualquer 2xx; falha de destino responde 502 e não entra no streak.

Contrato detalhado e exemplos em Webhooks.

Contrato de saúde do webhook

active é a intenção manual, enquanto health_statusé automático: unverified, healthy, degraded ou paused. Um endpoint novo não admite eventos reais até o teste POST /api/v1/webhooks/{id}/testretornar 2xx. Para reativar, use esse teste depois de corrigir o destino;PATCH { "active": true } sozinho retorna verification_required.

Depois de verificado, o retry segue 1 / 2 / 4 / 8 / 16 / 32 / 60 minutos(60 minutos repetidos). Alertas ao owner cruzam os streaks 5, 10 e 15; no 15º o endpoint entra em paused, sem escrever active=false. O backlog fica retido por 14 dias e é acordado por um novo teste 2xx, se não tiver expirado.

Leituras e o PATCH expõem os campos sanitizados health_status, failure_streak, verified_at, last_success_at, last_failure_at, last_response_code, next_attempt_at e paused_at. O secret não aparece nessas leituras.

Entregas de webhook

Status inválido no filtro responde 422 invalid_status. pending lista o que ainda aguarda a próxima tentativa.

MétodoCaminhoEscopoDescrição
GET/api/v1/webhook_deliverieswebhooks:readTentativas de entrega. Filtros: endpoint_id (ou webhook_id), status (pending, success, failed, exhausted) e event_type (nome fino, ex. whatsapp.message.received; categorias não casam). Paginado por cursor.

Contrato detalhado e exemplos em Webhooks.

Números e Contas de canal

Toda leitura de contato, conversa e mensagem traz channel e channel_account (id, channel, display), por onde a conversa acontece, e a mensagem traz external_id, o id dela no canal (no WhatsApp, o mesmo valor do wamid).

Template, transmissão, saúde de número e reengajamento por template só existem no WhatsApp. Informar uma Conta de canal de outro canal nesses recursos responde 422 channel_not_supported. /api/v1/phone_numbers continua inalterado: quem só fala WhatsApp não precisa migrar para /api/v1/channel_accounts.

Em /health, readiness.state é ready, reconnect_required, blocked, unknown, unavailable ou sandbox. É a mesma decisão do painel: conexão revogada ou bloqueada pela Meta nunca vira healthy por causa de um token válido ou de um status antigo.

O label do PATCH aceita até 100 caracteres. Leituras de número trazem label (ou null) ao lado de display_phone_number e verified_name, que continuam vindo da Meta.

Dar um nome local a um número

Grava o `label` do número, um nome só seu para identificar o número no BotoZap. Os dados da Meta (`display_phone_number`, `verified_name`, `quality_rating`) não mudam. Envie `label: null` ou texto vazio para limpar.

curl https://botozap.com.br/api/v1/phone_numbers/5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11 \
  -X PATCH \
  -H "X-API-Key: $BOTOZAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Loja Centro"
}'

Resposta 200

{
  "data": {
    "id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "phone_number_id": "109876543210987",
    "display_phone_number": "+5511999990000",
    "verified_name": null,
    "label": "Loja Centro",
    "quality_rating": "UNKNOWN",
    "type": null,
    "waba_connection_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "customer_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "waba_id": "waba-xxxx",
    "connection_status": "active",
    "token_status": "unknown",
    "created_at": "2026-07-14T12:00:00.000Z"
  }
}

`{id}` aceita o UUID interno ou o `phone_number_id` da Meta. Número de outra conta responde 404.

`label` é aparado, vai até 100 caracteres e não aceita quebra de linha. Sem `label` no corpo, a resposta é 422 no_updatable_fields.

Numa Conta de canal do Instagram, o bloco instagram traz a saúde da conexão: connection_status, token_status, token_expires_at e token_refreshed_at. O token do Instagram vale 60 dias e a BotoZap o renova sozinha. token_status é ok, expiring (vence em até 7 dias), expired, invalid (a Meta recusou o token), revoked (conta desconectada) ou unknown (conexão pendente). Com expired, invalid ou revoked, os envios falham até alguém reconectar a conta em Canais. Nenhum token sai na resposta.

Saúde da conexão de uma Conta do Instagram

Na Conta de canal do Instagram, o bloco `instagram` diz se o token da conexão está válido, vencendo ou vencido, sem expor o token.

curl https://botozap.com.br/api/v1/channel_accounts/5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11 \
  -H "X-API-Key: $BOTOZAP_KEY"

Resposta 200

{
  "data": {
    "id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "channel": "instagram",
    "customer_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "display": "@lojaexemplo",
    "status": "active",
    "sandbox": false,
    "external_id": "17841400000000001",
    "created_at": "2026-07-14T12:00:00.000Z",
    "updated_at": "2026-07-14T12:00:00.000Z",
    "instagram": {
      "instagram_account_id": "17841400000000001",
      "username": "lojaexemplo",
      "name": "Loja Exemplo",
      "instagram_connection_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
      "connection_status": "active",
      "token_status": "ok",
      "token_expires_at": "2026-07-14T12:00:00.000Z",
      "token_refreshed_at": "2026-07-14T12:00:00.000Z"
    }
  }
}

`token_status`: `ok`, `expiring` (vence em até 7 dias; a renovação automática ainda tenta), `expired`, `invalid` (a Meta recusou o token), `revoked` (conta desconectada) ou `unknown` (conexão pendente). Com `expired`, `invalid` ou `revoked`, os envios falham até reconectar a conta em Canais.

`token_expires_at` é a validade do token atual (60 dias, renovada sozinha a cada ~30); `token_refreshed_at` é a última renovação bem-sucedida. Os dois ficam `null` quando não há registro.

O bloco `whatsapp` só aparece em Conta de canal do WhatsApp, e o `instagram` só na do Instagram.

MétodoCaminhoEscopoDescrição
GET/api/v1/channel_accountsnumbers:readLista as Contas de canal (os endpoints de mensagem da conta). Filtros channel e customer_id. Paginado por página. Aceita chave sandbox.
GET/api/v1/channel_accounts/{id}numbers:readConsulta uma Conta de canal pelo UUID interno ou pelo id do endpoint na Meta (external_id). Aceita chave sandbox.
GET/api/v1/phone_numbersnumbers:readLista os números conectados. Filtro customer_id. Paginado por página. Aceita chave sandbox.
GET/api/v1/phone_numbers/{id}numbers:readConsulta um número.
PATCH/api/v1/phone_numbers/{id}numbers:writeGrava o label, nome local do número (null ou vazio limpa). É o único campo editável; corpo sem label responde 422 no_updatable_fields.
DELETE/api/v1/phone_numbers/{id}numbers:writeDesconecta o número do nosso sistema (não desregistra na Meta). Recusa com 409 phone_in_use se houver conversas, mensagens, contatos ou transmissões associados.
GET/api/v1/phone_numbers/{id}/healthnumbers:readProntidão do número: status legado e readiness (state, reason, next_action, observed_at, stale).

Regras de comentário do Instagram

Use o UUID interno da Conta de canal em {id}, obtido em GET /api/v1/channel_accounts. Cada regra pertence a uma única Conta de canal; IDs de outra Conta, canal ou ambiente respondem 404.

dm_text é obrigatório na criação, até 1000 bytes. Os campos opcionais são keyword (até 60 caracteres; null ou vazio significa qualquer comentário), reply_text (até 2200 caracteres; null ou vazio desliga a resposta pública), media_id (ID numérico do post, até 32 dígitos; null ou vazio vale para todos os posts) e is_active (booleano, padrão true). O PATCH preserva os campos omitidos. Uma combinação duplicada de palavra-chave e post responde 409 duplicate_rule.

Criar ou ativar exige plano pago reconhecido, trial vigente ou graça legada do Free até 31/10/2026. Sem esse direito, a API responde 422 plan_restricted no envelope padrão. Leitura, edição e desativação continuam disponíveis. O estado retornado é active, inactive ou paused_plan; paused_by_plan indica a pausa e is_active preserva a intenção configurada. Ao recuperar o direito, as regras ativas voltam sem reconfiguração.

notice traz o aviso de alcance quando a regra responde a qualquer comentário em todos os posts; nos demais casos é null. As respostas também incluem id, channel_account_id, created_at e updated_at. Chaves sandbox só gerenciam regras de Contas de canal sandbox, que nunca fazem envios reais.

Criar uma Regra de comentário

curl https://botozap.com.br/api/v1/channel_accounts/5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11/comment-rules \
  -X POST \
  -H "X-API-Key: $BOTOZAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "keyword": "boto",
  "dm_text": "Aqui está o link",
  "reply_text": "Enviei no direct",
  "media_id": "17895695668004550"
}'

Resposta 201

{
  "data": {
    "id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "channel_account_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "keyword": "boto",
    "dm_text": "Aqui está o link",
    "reply_text": "Enviei no direct",
    "media_id": "17895695668004550",
    "is_active": true,
    "status": "active",
    "paused_by_plan": false,
    "notice": null,
    "created_at": "2026-07-14T12:00:00.000Z",
    "updated_at": "2026-07-14T12:00:00.000Z"
  }
}

Use o UUID interno da Conta de canal do Instagram. Criar exige o BotoZap, trial vigente ou graça legada do Free (até 31/10/2026); a recusa é 422 plan_restricted.

Desativar uma Regra de comentário

curl https://botozap.com.br/api/v1/channel_accounts/5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11/comment-rules/5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11 \
  -X PATCH \
  -H "X-API-Key: $BOTOZAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "is_active": false
}'

Resposta 200

{
  "data": {
    "id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "channel_account_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "keyword": "boto",
    "dm_text": "Aqui está o link",
    "reply_text": "Enviei no direct",
    "media_id": "17895695668004550",
    "is_active": false,
    "status": "inactive",
    "paused_by_plan": false,
    "notice": null,
    "created_at": "2026-07-14T12:00:00.000Z",
    "updated_at": "2026-07-14T12:00:00.000Z"
  }
}

Desativar preserva a regra e seu histórico. Para ativar, envie is_active: true; sem direito de operar a API recusa com plan_restricted.

MétodoCaminhoEscopoDescrição
GET/api/v1/channel_accounts/{id}/comment-rulescomment-rules:readLista as regras da Conta de canal. Paginado por página. Aceita chave sandbox.
POST/api/v1/channel_accounts/{id}/comment-rulescomment-rules:writeCria uma regra. Responde 201. Aceita chave sandbox.
GET/api/v1/channel_accounts/{id}/comment-rules/{ruleId}comment-rules:readConsulta uma regra e seu estado de operação. Aceita chave sandbox.
PATCH/api/v1/channel_accounts/{id}/comment-rules/{ruleId}comment-rules:writeEdita os campos informados ou ativa/desativa com is_active. Aceita chave sandbox.

Mídia

O media_id devolvido por POST /api/v1/media vale por cerca de 30 dias e fica atrelado ao número. Use-o no cabeçalho de um template ({ "id": "..." } dentro de template.components); o envio direto de mídia em POST /api/v1/messages aceita só link. Arquivos maiores que 4,5 MB: use source.

GET /api/v1/media/{id} consulta mídia recebida, pelo media_id da Cloud API que chega na mensagem recebida. No Instagram, use o media.id que vem em instagram.message.received; 410 media_unavailable diz que a URL da Meta expirou antes do espelho, sem retry. No sandbox, só o id sandbox-media-0001 responde.

MétodoCaminhoEscopoDescrição
POST/api/v1/mediamedia:writeSobe uma mídia para a Meta por source (URL https) ou multipart/form-data com file (até ~4,5 MB), sempre com phone_number_id. O media_id devolvido serve no cabeçalho de template; delivery=meta_resumable_asset devolve um handle para criar template.
GET/api/v1/media/{id}media:readConsulta uma mídia recebida: mime_type, file_size, sha256, download_url assinada (1 hora) e expires_at. Ainda espelhando: 202 media_not_ready com Retry-After. Aceita chave sandbox.
GET/api/v1/media/{id}/downloadmedia:readRedireciona (302) para o download da mídia recebida, com os mesmos estados da rota acima. Aceita chave sandbox.

Contrato detalhado e exemplos em Mídia.

Usuários

MétodoCaminhoEscopoDescrição
GET/api/v1/usersteam:readLista os membros do workspace: id (= user_id), email, name e role (owner, admin ou member; no painel, owner e admin aparecem como Administrador e member como Operador). Paginado por página.

Logs de API

since é inclusivo e until exclusivo. environment=unknown traz os registros antigos, anteriores à coleta do ambiente. Filtro inválido responde 422 com código próprio: invalid_environment, invalid_phone, invalid_period ou invalid_status_code.

MétodoCaminhoEscopoDescrição
GET/api/v1/api_logslogs:readRequisições feitas à API. Filtros: environment (live ou unknown), phone_number_id (UUID interno), since/until (ISO 8601), source, method e status_code. Paginado por cursor.

Custo Meta

O valor é o aproximado que a Meta devolve, na moeda da WABA; a fatura da Meta é a autoridade. Nada é convertido e moedas diferentes vêm em grupos separados (totals, by_day, by_category, cada item com currency). A resposta traz source: "meta_pricing_analytics" e approximate: true. Cliente de outra Conta responde 404 not_found. Quando a Meta não devolve custo (WABA na linha de crédito de um parceiro), cost vem null, nunca 0, com unavailable: true e unavailable_reason: "cost_not_returned"; WABA ativa ainda não sincronizada responde unavailable_reason: "not_synced". Os dados são atualizados uma vez por dia e os últimos 7 dias são relidos para absorver revisões da Meta; sync.last_synced_at e sync.covered_from mostram o frescor e o primeiro dia coberto.

Estimativa. Cada grupo também traz estimated_cost e cost_source (meta, estimate, mixed ou null). Onde a Meta não informou custo, estimamos pelo volume de mensagens cobradas (REGULAR) × tarifa publicada pela Meta para destinatários no Brasil, na moeda da WABA (BRL ou USD); mensagens grátis (FREE_*) contam 0. Categoria ou moeda sem tarifa publicada conhecida (ex.: MARKETING_LITE) deixa estimated_cost: null — nada é inventado. O campo cost continua sendo só o valor da Meta. O objeto estimate informa a base (basis: "published_rates", market: "BR", rates_effective_from, rates_as_of, source_url), o que ela não considera (excludes: descontos por faixa de volume e destinatários fora do Brasil) e se cobre todo o recorte (available).

MétodoCaminhoEscopoDescrição
GET/api/v1/usage/meta-costscustomers:readVolume e custo das mensagens informados pela Meta (Pricing Analytics), por dia e por categoria. Query: customer_id (opcional), from e to (YYYY-MM-DD, inclusivos; padrão 30 dias; máximo 366).

Agente de IA

As 153 rotas /api/v1/ai cobrem agentes e versões, credenciais BYOK, provedores, conhecimento, memória, skills, follow-ups, roteadores, casos, alertas, avisos, propostas, execuções e orçamento. Use agents:read para leitura e agents:write para alterações; nenhuma aceita chave sandbox. A Conta vem da chave autenticada; o Cliente é informado em customer_id. A tabela completa, verificada contra o código, está em Agente de IA pela API. Rotas por recurso: agents (11), alerts (3), cases (8), commercial-proposals (6), credentials (9), eligibility (6), evolution (1), executions (3), followup-flows (10), followups (12), inferences (1), knowledge (18), memory (13), notices (8), operator-metrics (1), promises (1), proposals (5), providers (6), routers (8), skills (13), style-adjustments (2), uploads (2), usage (6).

O controle do agente numa conversa fica fora de /api/v1/ai:

MétodoCaminhoEscopoDescrição
POST/api/v1/conversations/{id}/agent-controlagents:writePausa ou retoma o agente de IA na conversa: { "action": "pause" | "resume" }.

Contrato detalhado e exemplos em Agente de IA pela API.