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.0Requer 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.
| Recurso | Métodos e rotas |
|---|---|
messagesEnvio 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. |
|
customersClientes da revenda e links de setup (Embedded Signup). |
|
templatesTemplates de mensagem submetidos à Meta. |
|
broadcastsTransmissões: rascunho, destinatários, agendamento e envio. |
|
contactsContatos por Número, com nome dado pela empresa (display_name), tags e vínculo ao Cliente. |
|
conversationsConversas (com origem entry_point, anúncio de clique em referral e janela grátis fep_*), resposta direta, controle do agente e atribuições. |
|
webhooksEndpoints de webhook, filtro por Cliente, rotação do secret e teste assinado. |
|
phoneNumbersNúmeros conectados, nome local (label) e saúde. |
|
channelAccountsContas 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. |
|
contactStagesEtapas do funil por Cliente. |
|
contactFieldsCampos personalizados da Conta. |
|
calendarGoogle Calendar autorizado no Painel: calendários, sincronização e conflitos. |
|
aiMó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. |
appointmentsAgenda: compromissos, disponibilidade e configuração (serviços, jornadas, exceções). |
|
journeysRéguas e suas execuções. |
|
savedRepliesRespostas salvas compartilhadas. |
|
inboxNotas internas, retornos, arquivo e adiamento de uma Conversa. |
|
opportunitiesOportunidades do CRM. |
|
demandsDemandas do CRM (mesmos métodos de opportunities). |
|
radarPendências do CRM e critérios por etapa. |
|
mediaIngestão de mídia por URL e metadados de mídia recebida. |
|
eventsReplay do log de Eventos por cursor. |
|
usageCusto aproximado da Meta por moeda, dia e categoria (Pricing Analytics). |
|
usersMembros da Conta (leitura). |
|
apiLogsAuditoria de requisições à API (leitura). |
|
webhookDeliveriesHistórico de Entregas de webhook (leitura). |
|
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 Contabroadcasts.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.
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.
