SDK Node

Envelope fino e tipado sobre a API REST, sem dependências de runtime.

Preview público 0.11.0

Os pacotes @botozap/sdk, @botozap/cli e @botozap/mcp estão publicados no npm em versão preview 0.x, sem promessa de estabilidade até a 1.0. A API REST continua sendo o fallback estável. Veja a referência da API /v1 e o guia de Enviar mensagens. No terminal, use a CLI; em agentes, o MCP.

O @botozap/sdk é a camada pública sobre a API HTTP: expõe os recursos da /v1 com tipos, monta a autenticação e traduz erros da API em exceções. O core da plataforma continua fechado.

Instalação

pnpm add @botozap/sdk@0.11.0

Requer Node 20.19 ou superior, por causa do fetch global. O pacote publica ESM, CommonJS e tipos TypeScript. Fixe a versão: enquanto o pacote estiver em 0.x, uma versão menor pode mudar assinaturas.

Início rápido

import { BotoZap } from "@botozap/sdk";

const boto = new BotoZap({ apiKey: process.env.BOTOZAP_API_KEY! });

const result = await boto.messages.send({
  to: "+5531988887777",
  text: "Seu pão tá saindo do forno",
});

console.log(result.wamid, result.status);

A conta é derivada da chave de API. Você nunca passa um id de conta: o isolamento multi-tenant é garantido no servidor. Detalhes em Autenticação.

O construtor aceita apiKey (obrigatória), baseUrl (padrão https://botozap.com.br/api/v1) e fetch, para injetar outra implementação em testes ou em runtimes sem fetch global.

Recursos e métodos

O cliente expõe 26 recursos como propriedades. A tabela mostra cada método e a rota da API /v1 que ele chama. Escopos, parâmetros e respostas ficam na referência.

RecursoMétodos e rotas
messages
Envio no WhatsApp e no Instagram (texto, template, mídia por link, botões e listas, localização e reação); histórico por cursor. No Instagram o recibo traz wamid null, channel "instagram" e o mid em external_id.
  • send → POST /messages: texto; quick_replies (até 13) só no Instagram
  • sendTemplate → POST /messages: template aprovado (só WhatsApp)
  • sendMedia → POST /messages: image, video, audio ou document por URL https
  • sendInteractive → POST /messages: botões, lista ou botão de link (só WhatsApp)
  • sendLocation → POST /messages: localização (só WhatsApp)
  • sendReaction → POST /messages: emoji numa mensagem; emoji vazio retira
  • list → GET /messages: content.edited ({ count, at }) e revoked_at em mensagens do Instagram
  • get → GET /messages/:id
customers
Clientes da revenda e links de setup (Embedded Signup).
  • list → GET /customers
  • get → GET /customers/:id
  • create → POST /customers
  • update → PATCH /customers/:id
  • delete → DELETE /customers/:id
  • listSetupLinks → GET /customers/:id/setup_links
  • createSetupLink → POST /customers/:id/setup_links
  • updateSetupLink → PATCH /customers/:id/setup_links/:linkId
templates
Templates de mensagem submetidos à Meta.
  • list → GET /templates
  • get → GET /templates/:id
  • create → POST /templates
broadcasts
Transmissões: rascunho, destinatários, agendamento e envio.
  • list → GET /broadcasts
  • get → GET /broadcasts/:id
  • create → POST /broadcasts
  • update → PATCH /broadcasts/:id
  • listRecipients → GET /broadcasts/:id/recipients
  • addRecipients → POST /broadcasts/:id/recipients: itens { to_recipient, components? }; falhas por item voltam em errors { index, to_recipient?, reason }
  • removeRecipients → DELETE /broadcasts/:id/recipients
  • schedule → POST /broadcasts/:id/schedule
  • send → POST /broadcasts/:id/send
  • cancel → POST /broadcasts/:id/cancel
contacts
Contatos por Número, com nome dado pela empresa (display_name), tags e vínculo ao Cliente.
  • list → GET /contacts
  • get → GET /contacts/:id
  • create → POST /contacts: display_name opcional, independente do profile_name do canal
  • update → PATCH /contacts/:id: display_name null ou "" limpa o nome
  • delete → DELETE /contacts/:id
conversations
Conversas (com origem entry_point, anúncio de clique em referral e janela grátis fep_*), resposta direta, controle do agente e atribuições.
  • list → GET /conversations: filtros channel (whatsapp | instagram) e channel_account_id
  • get → GET /conversations/:id
  • update → PATCH /conversations/:id
  • reply → GET /conversations/:id + POST /messages: resolve o Contato e a origem (Número no WhatsApp, Conta do Instagram) e envia texto; aceita quick_replies no Instagram
  • controlAgent → POST /conversations/:id/agent-control: pause | resume
  • listAssignments → GET /conversations/:id/assignments
  • createAssignment → POST /conversations/:id/assignments
  • getAssignment → GET /conversations/:id/assignments/:assignmentId
  • updateAssignment → PATCH /conversations/:id/assignments/:assignmentId
webhooks
Endpoints de webhook, filtro por Cliente, rotação do secret e teste assinado.
  • list → GET /webhooks
  • get → GET /webhooks/:id
  • create → POST /webhooks: customer_id opcional limita as entregas a um Cliente
  • update → PATCH /webhooks/:id: secret rotaciona; customer_id null remove o filtro
  • delete → DELETE /webhooks/:id
  • test → POST /webhooks/:id/test
phoneNumbers
Números conectados, nome local (label) e saúde.
  • list → GET /phone_numbers
  • get → GET /phone_numbers/:id
  • update → PATCH /phone_numbers/:id: só o label; null ou "" limpa
  • delete → DELETE /phone_numbers/:id
  • health → GET /phone_numbers/:id/health
channelAccounts
Contas de canal (Número do WhatsApp ou Conta do Instagram) e Regras de comentário do Instagram. O bloco instagram traz a saúde do token: token_status ok | expiring | expired | invalid | revoked | unknown.
  • list → GET /channel_accounts: filtros channel e customer_id; scope numbers:read
  • get → GET /channel_accounts/:id: UUID ou id na Meta (external_id)
  • listCommentRules → GET /channel_accounts/:id/comment-rules: scope comment-rules:read
  • getCommentRule → GET /channel_accounts/:id/comment-rules/:ruleId
  • createCommentRule → POST /channel_accounts/:id/comment-rules: scope comment-rules:write; plano com Regras de comentário
  • updateCommentRule → PATCH /channel_accounts/:id/comment-rules/:ruleId: só os campos enviados; is_active false desliga
contactStages
Etapas do funil por Cliente.
  • list → GET /contact-stages
  • create → POST /contact-stages
  • update → PATCH /contact-stages/:id
  • delete → DELETE /contact-stages/:id
  • reorder → PATCH /contact-stages/reorder
contactFields
Campos personalizados da Conta.
  • list → GET /contact-fields
  • create → POST /contact-fields
  • update → PATCH /contact-fields/:id
  • delete → DELETE /contact-fields/:id
calendar
Google Calendar autorizado no Painel: calendários, sincronização e conflitos.
  • connections → GET /calendar/connections
  • disconnect → DELETE /calendar/connections/:id
  • calendars → GET /calendar/connections/:id/calendars
  • refresh → POST /calendar/connections/:id/refresh
  • select → PATCH /calendar/selections/:id
  • jobs → GET /calendar/jobs
  • retryJob → POST /calendar/jobs/:id/retry
  • resolveConflict → POST /calendar/conflicts/:appointmentId/resolve
ai
Módulo de IA BYOK: 22 sub-recursos e 149 operações tipadas, mais invoke(group, name, input).
149 operações em /ai/*, agrupadas em 22 sub-recursos (uploads, agents, credentials, providers, executions, knowledge, memory, skills, followupFlows, followups, routers, cases, alerts, notices, proposals, usage, commercialProposals, eligibility, inferences, operator, evolution, styleAdjustments). Veja Agente de IA.
appointments
Agenda: compromissos, disponibilidade e configuração (serviços, jornadas, exceções).
  • list → GET /appointments
  • get → GET /appointments/:id
  • create → POST /appointments
  • update → PATCH /appointments/:id
  • delete → DELETE /appointments/:id
  • history → GET /appointments/:id/history
  • availability → GET /appointments/availability
  • services.list → GET /appointments/services
  • services.create → POST /appointments/services
  • services.update → PATCH /appointments/services/:id
  • schedules.list → GET /appointments/schedules
  • schedules.create → POST /appointments/schedules
  • schedules.update → PATCH /appointments/schedules/:id
  • exceptions.list → GET /appointments/exceptions
  • exceptions.create → POST /appointments/exceptions
  • exceptions.update → PATCH /appointments/exceptions/:id
  • exceptions.delete → DELETE /appointments/exceptions/:id
journeys
Réguas e suas execuções.
  • list → GET /journeys
  • get → GET /journeys/:id
  • create → POST /journeys
  • update → PUT /journeys/:id
  • delete → DELETE /journeys/:id: arquiva, preservando histórico
  • control → POST /journeys/:id/control
  • runs → GET /journeys/:id/runs
  • enroll → POST /journeys/:id/runs
  • getRun → GET /journey-runs/:id
  • controlRun → POST /journey-runs/:id/control
savedReplies
Respostas salvas compartilhadas.
  • list → GET /saved-replies
  • get → GET /saved-replies/:id
  • create → POST /saved-replies
  • update → PATCH /saved-replies/:id
  • delete → DELETE /saved-replies/:id
inbox
Notas internas, retornos, arquivo e adiamento de uma Conversa.
  • get → GET /conversations/:id/tools
  • mutate → PATCH /conversations/:id/tools
opportunities
Oportunidades do CRM.
  • list → GET /opportunities
  • get → GET /opportunities/:id
  • create → POST /opportunities
  • update → PATCH /opportunities/:id
  • activities → GET /opportunities/:id/activities
  • conversations → GET /opportunities/:id/conversations
  • linkConversation → POST /opportunities/:id/conversations
  • unlinkConversation → DELETE /opportunities/:id/conversations
demands
Demandas do CRM (mesmos métodos de opportunities).
  • list → GET /demands
  • get → GET /demands/:id
  • create → POST /demands
  • update → PATCH /demands/:id
  • activities → GET /demands/:id/activities
  • conversations → GET /demands/:id/conversations
  • linkConversation → POST /demands/:id/conversations
  • unlinkConversation → DELETE /demands/:id/conversations
radar
Pendências do CRM e critérios por etapa.
  • list → GET /radar
  • stageRules → GET /radar/stage-rules
  • configureStageRule → PUT /radar/stage-rules/:stageId
media
Ingestão de mídia por URL e metadados de mídia recebida.
  • upload → POST /media: a BotoZap baixa a URL source e devolve media_id ou handle
  • get → GET /media/:id: inclui download_url assinada; 202 vira media_not_ready
events
Replay do log de Eventos por cursor.
  • list → GET /events: external_id é o id da mensagem no canal; message_id é o wamid ou null; instagram.message.edited e .revoked chegam aqui e no webhook
usage
Custo aproximado da Meta por moeda, dia e categoria (Pricing Analytics).
  • metaCosts → GET /usage/meta-costs: scope customers:read; customer_id, from e to opcionais; custo ausente fica null, nunca 0
users
Membros da Conta (leitura).
  • list → GET /users
apiLogs
Auditoria de requisições à API (leitura).
  • list → GET /api_logs
webhookDeliveries
Histórico de Entregas de webhook (leitura).
  • list → GET /webhook_deliveries

boto.ai também oferece invoke(group, name, input), que executa só operações declaradas no catálogo do SDK. O cliente tem putArtifact(url, contentType, file) para enviar um arquivo à URL assinada devolvida por ai.uploads.prepare.

Exemplos de uso

await boto.messages.sendTemplate({
  to: "+5531988887777",
  template: { name: "boas_vindas", language: { code: "pt_BR" } },
});

await boto.messages.sendMedia({
  to: "+5531988887777",
  type: "document",
  link: "https://cdn.exemplo.com/fatura.pdf",
  caption: "Fatura de agosto",
  filename: "fatura-agosto.pdf",
});

// Responde a Conversa sem montar to/from: o SDK lê a Conversa e envia texto.
await boto.conversations.reply(conversationId, { text: "Já te respondo!" });

const broadcast = await boto.broadcasts.create({
  name: "Promo de sexta",
  phone_number_id,
  template_name: "promo_sexta",
  template_language: "pt_BR",
});
const { added, duplicates, errors } = await boto.broadcasts.addRecipients(broadcast.id, [
  { to_recipient: "+5531988887777" },
  {
    to_recipient: "+5531977776666",
    components: [{ type: "body", parameters: [{ type: "text", text: "Ana" }] }],
  },
]);
for (const e of errors) console.warn(e.index, e.to_recipient, e.reason);
await boto.broadcasts.send(broadcast.id);

const { data: numeros } = await boto.phoneNumbers.list({ customer_id });
await boto.phoneNumbers.update(numeros[0].id, { label: "Loja Centro" }); // null limpa

// Custo aproximado da Meta por moeda; custo ausente vem null, nunca 0.
const custos = await boto.usage.metaCosts({ from: "2026-09-01", to: "2026-09-24" });
for (const total of custos.totals) console.log(total.currency, total.cost ?? "indisponível");

// Endpoint que só recebe as entregas de um Cliente da Conta.
const hook = await boto.webhooks.create({
  url: "https://seu-dominio.example/webhooks/botozap",
  events: ["messages", "statuses"],
  customer_id,
});
await boto.webhooks.update(hook.id, { secret: novoSecret }); // rotaciona
await boto.webhooks.update(hook.id, { customer_id: null }); // volta a receber de toda a Conta

broadcasts.addRecipients aceita até 1000 objetos { to_recipient, components? } por chamada, com os parâmetros do template de cada destinatário; strings soltas não são aceitas. Item inválido não derruba o lote: ele volta em errors como { index, to_recipient?, reason }, e as repetições contam em duplicates.

Em webhooks.update, secret rotaciona o secret de assinatura (16 a 256 caracteres, sem quebra de linha) e não volta na resposta; veja Rotacionar o secret. customer_id limita as entregas a um Cliente da Conta, e null no update remove o filtro.

Instagram

O mesmo messages.send manda direct no Instagram: o to é o IGSID do contato, e com mais de uma conta do Instagram ativa o from é o UUID da Conta de canal. O recibo do Instagram traz wamid: null, channel: "instagram" e o mid da Meta em external_id; ramifique por channel, não pela presença de wamid. As regras do canal (janela, etiqueta de atendente humano, arquivos) estão em Enviar para o Instagram.

const sent = await boto.messages.send({
  to: igsid,
  text: "Qual tamanho você procura?",
  quick_replies: [
    { title: "P", payload: "TAM_P" },
    { title: "M", payload: "TAM_M" },
    { content_type: "user_email" },
  ],
});
console.log(sent.channel, sent.external_id); // "instagram", mid da Meta

// Só as conversas do Instagram; reply usa a Conta do Instagram como origem.
const { data: directs } = await boto.conversations.list({ channel: "instagram" });
await boto.conversations.reply(directs[0].id, { text: "Já te respondo!" });

// Contas de canal: o bloco instagram traz a saúde do token.
const { data: contas } = await boto.channelAccounts.list({ channel: "instagram" });
for (const conta of contas) console.log(conta.display, conta.instagram?.token_status);

// Regra de comentário: quem comentar "quero" no post recebe o direct.
await boto.channelAccounts.createCommentRule(contas[0].id, {
  keyword: "quero",
  dm_text: "Oi! Te mandei o link por aqui.",
  media_id: postId,
});

token_status vale ok, expiring (vence em até 7 dias; a renovação automática ainda tenta), expired, invalid, revoked ou unknown; com expired, invalid ou revoked os envios falham até a conta ser reconectada. quick_replies (até 13) só existem no Instagram. Em messages.list e messages.get, a mensagem do Instagram editada pelo contato traz content.edited ({ count, at }) e a apagada traz revoked_at; os Eventos instagram.message.edited e instagram.message.revoked chegam por events.list e pelo webhook, como os demais.

Paginação

As listas por cursor (messages, contacts, conversations, webhooks, journeys, apiLogs, webhookDeliveries) devolvem { data, paging }. Para a próxima página, passe paging.next em after. As listas por página usam page/per_page e devolvem { data, meta }. Listas curtas, como contactStages.list e contactFields.list, devolvem o array direto. Rotas de item são desempacotadas: o método devolve a entidade, sem o envelope { data }.

events.list é o replay do stream de Eventos: leia até paging.has_more ficar falso e persista paging.cursor para a próxima leitura.

let after = cursorSalvo ?? "0";
while (true) {
  const page = await boto.events.list({ after, limit: 100 });
  for (const event of page.data) console.log(event.type, event.id, event.external_id);
  after = page.paging.cursor;
  if (!page.paging.has_more) break;
}

Em cada Evento de mensagem, external_id é o identificador da mensagem no canal (o wamid no WhatsApp). message_id continua sendo o wamid, mas vem null fora do WhatsApp; os dois são null em Eventos sem mensagem, como os de CRM.

O que o SDK não cobre

Estas rotas da API /v1 não têm método dedicado no 0.11.0:

  • GET/POST /contacts/:id/consents: consentimentos do Contato.
  • GET /media/:id/download: proposital: media.get já devolve a mesma download_url, sem o redirect 302.

Para chamá-las com a mesma autenticação e o mesmo tratamento de erro, use o método de baixo nível request, com o caminho relativo à URL base:

const consents = await boto.request("GET", `/contacts/${contactId}/consents`);

Tratamento de erro

Respostas fora de 2xx, ou com o envelope { error: { code, message } } mesmo em 2xx, viram BotoZapError com code, message, status HTTP e os headers da resposta. Use os headers para ler Retry-After num 429 ou no 202 media_not_ready de media.get. Falha de rede vira network_error e resposta fora do contrato vira malformed_response, ambos com status 0. Veja a Referência.

import { BotoZap, BotoZapError } from "@botozap/sdk";

try {
  await boto.messages.send({ to: "+5531988887777", text: "Olá!" });
} catch (err) {
  if (err instanceof BotoZapError) {
    console.error(err.code, err.status, err.message, err.headers?.["retry-after"]);
  } else {
    throw err;
  }
}

Repositório e exemplos

O código do SDK, da CLI, do MCP e os exemplos vivem em github.com/bytecraft-fernando/botozap-js. O diretório examples/ traz projetos mínimos e rodáveis, com a chave sempre em variável de ambiente:

  • node-javascript: envio no sandbox, leitura da resposta e BotoZapError.
  • node-typescript: envio tipado, leitura paginada por cursor e erro estruturado.
  • webhook-receiver: valida o X-Webhook-Signature do BotoZap (HMAC-SHA256 hex sobre os bytes crus), deduplica por X-Idempotency-Key na mesma transação do efeito e só então responde 2xx.
  • agent-endpoint: Endpoint durável com fila PostgreSQL, worker de agente por LISTEN/NOTIFY e resposta pela API.
  • mcp-sandbox: configuração mínima do @botozap/mcp com chave sandbox.

Para receber Eventos, veja Webhooks e o Endpoint para agentes.