# Blaber > Super conector de APIs de mensageria/social para developers e AI agents. Uma única API REST consistente abstrai 9 canais: WhatsApp, Telegram, Instagram, Messenger, Custom (seu próprio chat), Slack, Discord, Threads e X (Twitter). Você integra uma vez — mesmos verbos, payloads, erros e webhooks para todas as plataformas. Este arquivo é o contexto COMPLETO para um LLM operar e depurar o Blaber; a fonte formal do contrato é o OpenAPI. ## URLs oficiais - API REST: `https://api.getblaber.com/v1` - OpenAPI 3.1 (contrato, fonte da verdade): `https://api.getblaber.com/openapi.yaml` - MCP server hospedado (Streamable HTTP): `https://mcp.getblaber.com/mcp` - Docs interativas (Scalar): `https://getblaber.com/docs` - Dashboard: `https://getblaber.com` - SDK TypeScript (Node + browser): `npm i @blaber/sdk` ## Autenticação - **API key Bearer**: header `Authorization: Bearer bk_...` (formato `bk_` + 64 hex). Crie em Dashboard → Settings → API Keys. Escopo por profile; escopos granulares por recurso (`messages:write`, `connect:write`, `contacts:read`...). - **MCP**: mesma API key, no header da conexão (`Authorization: Bearer bk_...`). - **Dashboard**: Supabase Auth (JWT) — só para a sessão de usuário na UI. A API pública é sempre por `bk_`. - Erros de auth: 401 `missing_api_key` (header ausente), 401 `invalid_api_key` (key inválida/revogada), 403 `missing_scope` (key sem o escopo da rota), 403 `profile_forbidden` (recurso de outro profile). ## Convenções da API - **JSON camelCase** em requests e responses (padrão Zernio). Ex.: `accountId`, `templateId`, `quickReplies`. (Internamente o banco é snake_case; a conversão é transparente e há aliases de leitura.) - **IDs**: ULID prefixado por tipo — `prof_` (profile) `acc_` (account) `conv_` (conversation) `cont_` (contact) `msg_` `tmpl_` `bcast_` `flow_` `phon_` `media_` `key_` `whk_` `vnum_` `cchan_` (canal custom). Prefixo errado → 400 `invalid_parameter`. - **Paginação**: cursor — `?limit=&cursor=`; resposta `{ object: "list", data: [...], hasMore, nextCursor }`. - **Idempotência**: header `Idempotency-Key: ` nos POSTs sensíveis (envio de mensagem, broadcast) — repetir a mesma key devolve a resposta original, sem duplicar. - **Rate limit**: 429 `rate_limited` com `Retry-After` quando aplicável. - **Modelo de recursos**: Organization → **Profile** (workspace isolado; toda key e recurso pertencem a um) → **Accounts** (uma conexão de canal) → recursos por canal (conversas, mensagens, templates...). Um profile pode ter várias accounts, inclusive do mesmo canal. ## Tarefas comuns (endpoints principais) - Conectar canal: ver a seção do canal abaixo (`POST /v1/connect/`...) - Estado de uma conexão: `GET /v1/connect//status?accountId=acc_...` - Listar conexões: `GET /v1/accounts` (campo `platform` diz o canal) - Resolver/criar conversa: `POST /v1/inbox/create-conversation` `{ accountId?, to }` (sem `accountId` usa a account default do profile) - Enviar mensagem: `POST /v1/inbox/conversations/{conversationId}/send` (corpo flat, ver abaixo) - Reagir: `POST /v1/inbox/conversations/{conversationId}/react` `{ emoji, messageId? }` (só WhatsApp/Telegram/Slack) - Listar conversas: `GET /v1/inbox/conversations` (filtros: `platform`, `accountId`, `unread`) - Mensagens de uma conversa: `GET /v1/inbox/conversations/{id}/messages` - Contatos (CRM): `GET/POST /v1/contacts`, import em massa `POST /v1/contacts/import` - Templates WhatsApp: `GET/POST/PATCH/DELETE /v1/whatsapp/templates` - Broadcasts: `POST /v1/broadcasts` (+ `/send`), status/stats no recurso - Webhooks (callbacks p/ seu sistema): `GET/POST/PATCH/DELETE /v1/webhooks` + `POST /v1/webhooks/{id}/test` - Números virtuais (BR): `GET/POST /v1/numbers`, áreas `GET /v1/numbers/area-codes` - Mídia: `POST /v1/media` (upload) → use `mediaId` nos attachments - Billing: `GET /v1/billing`, métodos de pagamento, faturas ## Enviar mensagens — corpo flat (todos os canais) `POST /v1/inbox/conversations/{id}/send` com QUALQUER combinação válida de: - `text` — string (limites por canal abaixo) - `attachments` — até 10 de `{ type: image|video|audio|document|sticker, url OU mediaId, caption?, filename? }` (exatamente um entre `url`/`mediaId`) - `quickReplies` — até 13 de `{ title (≤24), payload? }` (WhatsApp/Messenger/Instagram/Telegram) - `buttons` — até 3 de `{ type: url|postback|phone, title, url?, payload?, phoneNumber? }` - `template` — `{ templateId OU name+language, components? }` (SÓ WhatsApp; é a única saída fora da janela de 24h) - `replyMarkup` — teclado nativo do Telegram (passthrough opaco) - `messageTag` — tag para enviar fora da janela (SÓ Messenger, ex.: `ACCOUNT_UPDATE`) ### Janela de 24h por canal (regra central de envio) | Canal | Janela 24h? | Fora da janela | | --- | --- | --- | | WhatsApp | SIM | só `template` aprovado | | Instagram | SIM | não há saída — aguardar nova mensagem do cliente | | Messenger | SIM | `messageTag` válida (via API; a UI não envia tag) | | Telegram, Custom, Slack, Discord, Threads, X | NÃO | envio livre sempre | Enviar mensagem livre fora da janela → 400 `message_outside_24h_window`. A janela abre/renova a cada mensagem INBOUND do contato. ## Canais — detalhes que resolvem 90% das dúvidas ### WhatsApp (Cloud API oficial da Meta) - Conectar: `POST /v1/connect/whatsapp` → devolve `fbAppId`, `configId`, `state` p/ o popup do Embedded Signup (FB.login) → `POST /v1/connect/whatsapp/exchange` `{ state, code, wabaId, phoneNumberId? }`. Cria o WABA do cliente pela plataforma. - `to`: telefone E.164 (`+5511999999999`). `wamid` = id da mensagem na Meta. - Registro de número: `request-code`/`verify-code`/`register` em `/v1/connect/whatsapp/...`; saúde do número em `GET /v1/phone-numbers`. - Suporta: texto, mídia, interativas (botões/listas), localização, contatos, reações, templates, flows, business profile. - Erros típicos: `number_in_use` (número já conectado em outro workspace → 409), `meta_token_expired` (reconectar), `advanced_access_required` (app Meta sem App Review — só admins/testers em modo dev). ### Telegram (bot da plataforma — conexão "1 toque") - Conectar: `POST /v1/connect/telegram` → devolve `telegramConnect.deepLink` (`t.me/?start=...`); o usuário abre e toca "Iniciar". `GET /v1/connect/telegram/status` para saber quando `connected: true`. - `to`: `chat_id` numérico (vem no `from` do primeiro `message.received`, ou em `GET /v1/contacts` como `externalId`). - Sem janela de 24h. Suporta texto, mídia por URL, `quickReplies`/`buttons` (inline keyboard) e `replyMarkup` nativo. Reações suportadas. - Modelo: UM bot Blaber atende todos os tenants — o contato conversa com o bot da plataforma. ### Instagram (DM — "API with Instagram Login") - Conectar: `POST /v1/connect/instagram` `{ redirectUri }` → `authorizeUrl` (popup) → callback devolve `code`+`state` → `POST /v1/connect/instagram/exchange` `{ state, code, redirectUri }`. - `to`: **IGSID** (id escopado do usuário — SÓ vem do inbound; não dá para iniciar conversa com @username). - Janela de 24h SEM template: fora dela, aguardar o cliente. Token long-lived (~60d) renovado automaticamente (cron diário) — se expirar de vez, account vira `error` + evento `account.disconnected` (reconectar). - Conta precisa ser profissional com "acesso a mensagens" ligado. Requer App Review da Meta p/ contas fora do app (modo dev = admins/testers). ### Messenger (Páginas do Facebook) - Conectar: `POST /v1/connect/messenger` → popup FB Login → exchange; com 2+ Páginas o exchange devolve `isMessengerPagePicker: true` + `pagesToken` (`pt_`) → `POST /v1/connect/messenger/select` `{ pagesToken, pageId }`. - `to`: **PSID** (id escopado por Página — só vem do inbound). Credencial = Page token (não expira). - Fora da janela: `messageTag` (ex.: `ACCOUNT_UPDATE`, `POST_PURCHASE_UPDATE`, `CONFIRMED_EVENT_UPDATE`) — use com critério (política da Meta). - `channel_in_use` (409) se a Página já está em outro workspace. ### Custom (o SEU chat: bubble no site, embed ou web component) - Criar: `POST /v1/connect/custom` `{ name, requiredAttributes?, appearance?, allowedOrigins?, channels? }` → devolve `customChannel` com `publicKey` (`ck_`) + snippets. Editar: `PATCH /v1/connect/custom/config`. - Pré-chat configurável: atributos `{ key, label, type, required }` com `type` ∈ text|email|phone|date|integer|boolean|cpf|cnpj (validação incluída, máscaras no widget). - Aparência: cores (widget, chat, cabeçalho, bolhas do cliente/agente, ícone), fonte Google Fonts, posição do bubble (4 cantos), ícone preset ou upload. - Superfícies: **bubble** (script `widget.js` com `data-key`), **embed** (iframe `/widget/`), **web component** (``). - API pública do widget (função `widget-custom`; sem API key): sessão stateless `cwt_` (30d) aberta com a `publicKey`; visitante manda mensagem → cai no SEU inbox e dispara `message.received`; respostas do atendente chegam ao widget por polling. - `allowedOrigins` vazio = qualquer origem (só p/ dev). Sem janela de 24h. ### Slack (app instalado no workspace do cliente) - Conectar: `POST /v1/connect/slack` `{ redirectUri }` → `authorizeUrl` (OAuth v2, popup) → `POST /v1/connect/slack/exchange`. - Escopo v1: **DMs com o bot** (canais do workspace ficam de fora). `to`: user id (`U...`/`W...`) — o DM é aberto automaticamente. Id de mensagem: `":"`. - Sem janela. Reações: `POST .../react` com emojis comuns (👍❤️😂 etc. → nomes do Slack). - `channel_in_use` se o workspace (`team_id`) já está conectado em outro profile. `token_revoked` = app removido do workspace (reconectar). ### Discord (bot da plataforma no servidor do cliente) - Conectar: `POST /v1/connect/discord` `{ redirectUri }` → `authorizeUrl` (scope=bot) → exchange identifica o SERVIDOR. - Modelo: **conversa POR CANAL** do servidor — o "contato" é `#nome-do-canal`; o autor humano de cada mensagem vem em `fromName`. DMs diretas ao bot são ignoradas (bot compartilhado). - `to`: id do canal (snowflake — vem do inbound). Limite 2000 chars; anexos viram URLs anexadas ao texto. - Recebimento DEPENDE do worker de Gateway da plataforma (`workers/discord-gateway`) rodando 24/7 — se mensagens não chegam mas envio funciona, o worker está fora. ### Threads (Meta) — ATENÇÃO: replies PÚBLICAS - NÃO existe API de DM no Threads. O canal é um inbox das RESPOSTAS aos posts da conta conectada: **cada post raiz vira uma conversa** (contato = "Post "; autor da reply em `fromName`). - Responder = publicar uma **reply PÚBLICA** sob o post (qualquer pessoa vê). Limite 500 chars; só texto. - Conectar: `POST /v1/connect/threads` `{ redirectUri }` → popup "Login com Threads" → exchange. `to`: id do post raiz (vem do inbound). - Token long-lived renovado automaticamente (mesmo cron do Instagram). Sem janela de 24h (mas é público!). ### X / Twitter (DMs 1:1) - Conectar: `POST /v1/connect/x` `{ redirectUri }` → `authorizeUrl` (OAuth 2.0 + PKCE; o `state` é `xst_` e carrega o code verifier selado — devolva-o INTACTO no exchange) → `POST /v1/connect/x/exchange`. - `to`: id numérico do usuário do X (vem do inbound; @handle não é aceito). - Recebimento por **POLLING a cada ~2 min** (não há webhook de DM no tier atual) — o inbox do X não é instantâneo; mensagens novas aparecem em até 2 minutos. - Sem janela de 24h, mas o X impõe TETO DIÁRIO de DMs por conta → 429 `rate_limited`. Destinatário que não aceita DMs de não-seguidores → `recipient_not_available`. Texto até 10.000 chars; anexos ainda não. - Tokens (access ~2h + refresh rotativo) renovam sozinhos; `token_revoked` = usuário desautorizou o app (reconectar). ## Templates do WhatsApp (regras que evitam rejeição) - Criar: `POST /v1/whatsapp/templates` `{ name, language, category, components }`. `name` minúsculo snake_case; `language` ex. `pt_BR`; `category` ∈ marketing|utility|authentication (minúsculo — a conversão p/ o formato da Meta é automática). - `components`: header (text/image/video/document), body (obrigatório), footer, buttons (url/phone/quick reply). Variáveis `{{1}}`, `{{2}}`... no body/header. - **Variáveis exigem exemplos**: forneça amostras (a UI usa os previews; via API inclua `example` nos components ou textos de amostra) — sem exemplo a Meta REJEITA com `INVALID_FORMAT`. - Status: `draft → pending → approved|rejected` (motivo em `rejectionReason`; sincronizado por cron e webhook). Só `approved` pode ser enviado. - **Editar** um template já submetido re-submete a revisão (status volta a `pending`). Não tente recriar com o mesmo name+language — a edição usa a API correta da Meta por baixo ("There is already Portuguese (BR) content" era o sintoma do jeito errado). - Enviar: corpo flat `{ template: { templateId, components: { body: { variables: ["João", "123"] } } } }`. ## Broadcasts & contatos - Broadcast = template aprovado + audiência de contatos `optIn: opted_in` (compliance é ENFORÇADO: opted_out/unknown são pulados com erro por destinatário). - `POST /v1/broadcasts` `{ name, templateId, audience: { tags?/contactIds? }, variablesMap?, scheduledAt? }`; placeholders `{{contact.name}}`, `{{contact.custom_fields.x}}` no `variablesMap`. - Palavras-chave automáticas no inbound: STOP/PARAR/SAIR/CANCELAR → opt-out; START/VOLTAR → opt-in. - Contato: identidade genérica por canal = (`platform`, `externalId`) — E.164 no WhatsApp, chat_id no Telegram, IGSID/PSID na Meta, user/canal/post/user-id nos demais. ## Webhooks de saída (Blaber → seu sistema) - `POST /v1/webhooks` `{ url, events }`. Eventos: `message.received`, `message.status_updated`, `template.approved`, `template.rejected`, `account.connected`, `account.disconnected`, `broadcast.completed`, `flow.completed`. - Assinatura: header `Blaber-Signature: t=,v1=` onde `v1 = HMAC_SHA256(secret, ".")`. Valide em tempo constante; tolerância recomendada 300s (anti-replay). O `secret` só é exibido na criação. - Retries com backoff exponencial em falha (não-2xx); entregas auditáveis; `POST /v1/webhooks/{id}/test` dispara um evento de teste. ## Erros — taxonomia e correções Formato: `{ "error": { "type", "code", "message", "param?", "docUrl?", "requestId?", "providerError?" } }`. `providerError` traz o erro original da plataforma (Meta/Slack/X...) já mapeado. | code | HTTP | Correção | | --- | --- | --- | | `missing_api_key` / `invalid_api_key` | 401 | Header `Authorization: Bearer bk_...` correto | | `missing_scope` | 403 | Recrie a key com o escopo exigido (está na doc da rota) | | `profile_forbidden` | 403 | O recurso pertence a outro workspace | | `invalid_parameter` | 400 | `param` diz o campo; confira formato/prefixo do id | | `message_outside_24h_window` | 400 | WhatsApp: envie `template` aprovado; Messenger: `messageTag`; Instagram: aguarde o cliente | | `unsupported_message_type` | 400 | O canal não suporta esse conteúdo (ex.: template fora do WhatsApp, anexo no Threads/X v1) | | `channel_in_use` / `number_in_use` | 409 | A conta/número já está conectada em OUTRO workspace — desconecte lá primeiro | | `resource_not_found` | 404 | Id inexistente neste profile | | `meta_token_expired` / `token_revoked` | 401 | Reconectar o canal (Conectar → canal → refazer o login) | | `recipient_not_available` | 4xx | Destinatário bloqueia DMs (X: não-seguidores; Slack: usuário fora do workspace) | | `rate_limited` | 429 | Aguarde `Retry-After`; no X é o teto diário de DMs | | `payment_required` / conexão `payment_pending` | 402/— | Billing: confirme o cartão do workspace; conexões novas nascem bloqueadas até a cobrança confirmar | | `provider_error` | 422 | Erro da plataforma — inspecione `providerError.code/message` | ## MCP server (para AI agents) - Endpoint: `https://mcp.getblaber.com/mcp` (Streamable HTTP, stateless). - **Auth recomendada — OAuth automático (usuário leigo)**: adicione a URL no cliente MCP SEM token. O servidor responde 401 com `WWW-Authenticate` → o cliente descobre os metadados (`/.well-known/oauth-protected-resource`), registra-se (`/oauth/register`, DCR) e abre o browser em `getblaber.com/mcp/authorize`: a pessoa faz login OU CRIA A CONTA na hora, confirma a forma de pagamento (se pedida) e clica Autorizar → o cliente troca o code (`/oauth/token`, PKCE S256) por uma **API key `bk_` dedicada**, salva localmente. A key aparece como "MCP — " em Settings → API Keys (revogável a qualquer momento; sem o escopo `admin`). A key **expira em 90 dias** e o cliente a renova sozinho via `refresh_token` (grant `refresh_token`, com ROTAÇÃO: cada refresh revoga a key anterior e emite uma nova — reuso de um refresh antigo é recusado). Token vazado envelhece sozinho. - **Auth manual (alternativa)**: header `Authorization: Bearer bk_...` com uma key criada no dashboard. - Uma tool por operação do OpenAPI, prefixo `blaber_` (ex.: `blaber_send_conversation_message`, `blaber_list_conversations`, `blaber_connect_whatsapp`). ~92 tools; parâmetros de path/query no topo e o corpo JSON em `body`. - Claude/ChatGPT (Connectors): cole `https://mcp.getblaber.com/mcp` — o login abre sozinho. - Claude Code: `claude mcp add --transport http blaber https://mcp.getblaber.com/mcp` (o OAuth abre no browser; ou adicione `--header "Authorization: Bearer bk_..."` para pular o login). - Clientes stdio (Cursor/Desktop antigos): `npx mcp-remote https://mcp.getblaber.com/mcp` (mcp-remote conduz o OAuth; `--header` para o modo manual). - Troubleshooting: 401 persistente = autorização não concluída (refaça o fluxo; verifique se concluiu o passo do cartão); tool com `isError` de billing = workspace com pagamento pendente. ## SDK TypeScript ```ts import Blaber from "@blaber/sdk"; const blaber = new Blaber({ apiKey: "bk_..." }); // baseUrl default: https://api.getblaber.com/v1 const conv = await blaber.inbox.createConversation({ to: "+5511999999999" }); await blaber.inbox.send(conv.id, { text: "Olá!" }); // connect.*: whatsapp, telegram, instagram(+Exchange/Status), messenger(+Select), // custom(+Update), slack, discord, threads, x — todos com Exchange/Status. ``` ## Princípio de paridade Tudo que a dashboard faz, a API faz; tudo que a API faz, o MCP expõe. Um AI agent é cidadão de primeira classe. Em dúvida sobre um parâmetro: o OpenAPI (`https://api.getblaber.com/openapi.yaml`) é a fonte da verdade — descriptions escritas para LLMs. ## Blog (guias práticos, pt-BR) Guias de integração passo a passo em `https://getblaber.com/blog/` (HTML estático, com JSON-LD e FAQ por post; feed em `/blog/rss.xml`, sitemap em `/blog/sitemap-index.xml`): - Suporte multicanal sem código: https://getblaber.com/blog/canal-de-suporte-multicanal/ - WhatsApp oficial sem Business Manager (Embedded Signup): https://getblaber.com/blog/whatsapp-oficial-sem-sofrer/ - Janela de 24h do WhatsApp explicada: https://getblaber.com/blog/janela-24-horas-whatsapp/ - Templates do WhatsApp sem rejeição: https://getblaber.com/blog/templates-whatsapp-sem-rejeicao/ - Broadcasts com opt-in, sem banimento: https://getblaber.com/blog/broadcasts-sem-banimento/ - Telegram em 5 minutos (conexão 1 toque): https://getblaber.com/blog/bot-telegram-5-minutos/ - Discord como canal de suporte: https://getblaber.com/blog/discord-canal-de-suporte/ - Chat no site (widget Custom, apps vibecodados): https://getblaber.com/blog/chat-no-site-vibecoding/ - Blaber + n8n: https://getblaber.com/blog/blaber-com-n8n/ - Blaber + Make: https://getblaber.com/blog/blaber-com-make/ - Blaber + Zapier: https://getblaber.com/blog/blaber-com-zapier/ - Blaber + Activepieces: https://getblaber.com/blog/blaber-com-activepieces/ - Blaber + Typebot: https://getblaber.com/blog/blaber-com-typebot/ - O que é MCP (explicado pra leigos): https://getblaber.com/blog/o-que-e-mcp/ - AI agents respondendo clientes via MCP: https://getblaber.com/blog/atendimento-com-ia-mcp/