API da Brevo: guia prático para desenvolvedores
Guia da API da Brevo para desenvolvedores: autenticação, URL base, contatos, e-mail transacional, campanhas, objetos de CRM, webhooks, limites de taxa e limites reais.
A Brevo expõe uma única API REST que abrange mensagens transacionais, campanhas de marketing, dados de contato e registros de CRM. Fazer a primeira requisição devolver 201 leva cerca de dois minutos. Chegar a uma integração de produção que não perca dados em silêncio leva bem mais tempo, porque várias das restrições que mais importam são pouco documentadas ou contradizem o que a própria API informa sobre si mesma.
Este guia cobre as duas metades: os endpoints, SDKs e a autenticação de que você precisa no primeiro dia, e os limites da plataforma que você precisa contornar no desenho antes de colocar em produção.
O que a API da Brevo cobre
Tudo vive sob um único host e um único caminho de versão. A documentação para desenvolvedores agrupa a superfície em quatro áreas de produto:
- Mensagens: e-mail transacional, SMS e WhatsApp, incluindo envios em lote, agendamento e atividade das mensagens.
- Plataforma de marketing: contatos, listas, segmentos e campanhas de e-mail.
- E-commerce: produtos, pedidos e rastreamento de eventos do cliente.
- Conversas: o widget de chat e a gestão programática de conversas.
Essas áreas compartilham uma conta, um banco de contatos e uma chave de API. Isso é conveniente e ocasionalmente perigoso: um script escrito com uma ideia de dados de homologação está falando com os mesmos contatos para os quais suas campanhas enviam.
Transacional versus marketing
As duas famílias se comportam de forma bastante diferente, e confundi-las é o erro de design mais comum.
| Transacional | Marketing | |
|---|---|---|
| Endpoint principal | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Endereçamento | Destinatários explícitos na requisição | listIds ou segmentIds |
| Disparo | Sua aplicação, em tempo real | Agendado ou enviado sob demanda |
| Formato típico de volume | Contínuo, uma mensagem por vez | Em picos, um envio grande |
| Postura de limite de taxa | Muito alta, 1.000 requisições por segundo nos planos padrão | Baixa, os endpoints de campanha caem no limite geral |
Se você ainda está decidindo se a Brevo é a plataforma certa, a visão geral da plataforma cobre esse terreno.
Autenticação e gestão de chaves
A Brevo usa uma chave de API simples em um cabeçalho personalizado. O cabeçalho se chama api-key, não Authorization, e não existe prefixo Bearer. Isso derruba quase todo mundo que já usou outra API de mensagens antes.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"As chaves são geradas no app da Brevo, nas configurações da conta, na seção SMTP e API, na aba de chaves de API. Dê a cada chave um nome descritivo ligado ao sistema que a usa. O valor da chave aparece exatamente uma vez, no momento em que é gerada, então, se você perdê-lo, gera uma nova chave em vez de recuperar a antiga.
Algumas regras práticas:
- Emita uma chave separada por ambiente de deploy e por serviço. Revogar uma chave comprometida nunca deveria derrubar três sistemas sem relação entre si.
- As chaves de API padrão valem para a conta inteira. Trate qualquer chave como acesso total a contatos, envios e dados de CRM.
- A Brevo também é compatível com OAuth 2.0 para aplicações que agem em nome de outras contas Brevo, descrito junto ao fluxo de chaves em authentication schemes.
- O servidor MCP usado por assistentes de AI usa um token separado e esse sim usa um cabeçalho bearer. Esse token é gerado na mesma tela de chaves de API, mas não é intercambiável com uma chave REST.
URL base, versionamento e sua primeira escrita
A URL base é https://api.brevo.com/v3/. A versão fica no caminho, não em um cabeçalho, e a v3 é a geração atual. Todo caminho neste guia é relativo a essa base.
Uma primeira escrita é mais informativa que uma primeira leitura, porque exercita as partes da conta que costumam estar mal configuradas (remetentes verificados, em especial):
curl -X POST https://api.brevo.com/v3/smtp/email \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sender": { "name": "Ops", "email": "[email protected]" }, "to": [{ "email": "[email protected]", "name": "Dev" }], "subject": "First transactional send", "htmlContent": "<html><body><p>It works.</p></body></html>", "tags": ["smoke-test"] }'Um envio bem-sucedido devolve 201 com um messageId. Um envio agendado devolve 202.
Os endpoints que você vai usar de verdade
Contatos
POST /v3/contacts cria um contato. O corpo aceita email, um mapa attributes para campos personalizados, listIds, ext_id para sua própria chave externa, e as duas flags que mais importam na prática: updateEnabled, que transforma a chamada em um upsert, e getId, que faz a resposta devolver o id do contato.
As leituras passam por GET /v3/contacts, que pagina com limit (padrão 50, máximo 1000) e offset, e é compatível com modifiedSince e createdSince em UTC. Sincronizações incrementais devem se apoiar em modifiedSince em vez de percorrer a lista inteira. Note que o parâmetro filter só permite um operador de igualdade, então qualquer coisa mais expressiva pertence a um segmento.
Para carga em massa, POST /v3/contacts/import aceita fileUrl, fileBody ou jsonBody, aponta para listIds e roda de forma assíncrona, devolvendo um processId. A Brevo documenta um máximo de 10 MB por corpo e recomenda ficar perto de 8 MB, porque o parsing infla o payload. Informe notifyUrl para você saber o resultado em vez de ficar consultando.
E-mail transacional
POST /v3/smtp/email é o cavalo de batalha. Além de sender, to, subject e htmlContent, os campos que vale conhecer são:
templateIdcomparams, que substitui o conteúdo inline por um template da Brevo e suas substituições de variáveis. Os params de cada versão têm teto de 100 KB, e o acumulado, de 1000 KB.messageVersions, que envia variantes personalizadas em uma única chamada, com até 99 destinatários por versão.tags, que você deveria sempre definir. As tags voltam nos eventos de webhook e são a única forma barata de correlacionar um evento de entrega com o trecho de código que o produziu.scheduledAtmaisbatchId, para envios futuros que você talvez queira cancelar em grupo.headers, em Title-Case, para cabeçalhos SMTP personalizados.
Uma única requisição aceita no máximo 2.000 destinatários. Para a diferença entre esse endpoint e o envio de campanhas, o guia de e-mail transacional traz a visão de estratégia de mensagens.
Campanhas de e-mail
POST /v3/emailCampaigns exige name e sender, além de exatamente uma fonte de conteúdo: htmlContent (mínimo de 10 caracteres, abaixo de 1 MB), htmlUrl ou templateId. O público vai em recipients como listIds ou segmentIds, e scheduledAt usa o formato UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Rotas complementares cobrem envio imediato, envio de teste, atualização de status e obtenção do relatório da campanha.
Empresas, negócios e objetos
O CRM da Brevo tem dois caminhos de escrita que se sobrepõem, e escolher certo faz diferença.
As rotas de CRM são POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} e o conjunto equivalente para negócios. Elas são síncronas. Um PATCH devolve 204 assim que a mudança é aplicada.
A API de objetos é o caminho em massa: POST /v3/objects/{object_type}/batch/upsert aceita até 1000 registros e 1 MB por requisição, até 500 atributos por registro e até 10 registros de associação por tipo de objeto por registro. Ela devolve 202 com um processId, o que significa aceito, não aplicado.
curl -X POST https://api.brevo.com/v3/objects/company/batch/upsert \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ { "identifiers": { "id": 12345 }, "attributes": { "domain": "acme.example", "industry": "retail" } } ] }'O guia do Brevo CRM cobre o modelo de objetos do lado do operador.
SDKs oficiais
A Brevo mantém clientes na organização getbrevo do GitHub:
| Linguagem | Repositório |
|---|---|
| Node.js | github.com/getbrevo/brevo-node |
| Python | github.com/getbrevo/brevo-python |
| PHP | github.com/getbrevo/brevo-php |
| Java | github.com/getbrevo/brevo-java |
| C# | github.com/getbrevo/brevo-csharp |
| Go | github.com/getbrevo/brevo-go |
| Ruby | github.com/getbrevo/brevo-ruby |
O cliente Node é instalado como @getbrevo/brevo:
npm install @getbrevo/brevoimport { BrevoClient } from "@getbrevo/brevo";
const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY });
const result = await brevo.transactionalEmails.sendTransacEmail({ subject: "Order confirmed", htmlContent: "<html><body><p>Thanks for your order.</p></body></html>", tags: ["order-confirmation"],});
console.log("Message ID:", result.messageId);O cliente Python é instalado com pip install brevo-python. Se você preferir não carregar uma dependência de SDK por causa de dois endpoints, a superfície HTTP crua é pequena o bastante para ser chamada direto, o que também mantém você isolado da rotatividade de versões do SDK:
import osimport requests
BASE = "https://api.brevo.com/v3"HEADERS = { "api-key": os.environ["BREVO_API_KEY"], "Content-Type": "application/json",}
def upsert_contact(email, attributes, list_ids): response = requests.post( f"{BASE}/contacts", headers=HEADERS, json={ "email": email, "attributes": attributes, "listIds": list_ids, "updateEnabled": True, }, timeout=30, ) response.raise_for_status() return responseTambém existe um servidor MCP em https://mcp.brevo.com/v1/brevo/mcp para assistentes de AI, autenticado com um token bearer gerado na mesma tela de configurações. Ele é útil para exploração e perguntas sobre a conta, não para caminhos de dados em produção.
Webhooks
Os webhooks são como você fica sabendo o que aconteceu depois de um envio. POST /v3/webhooks cria um, com url, events, type e, opcionalmente, channel (email ou sms), batched, headers personalizados e um objeto auth.
Existem três tipos de webhook, com vocabulários de evento distintos:
- Transacional:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketing:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Entrada:
inboundEmailProcessedereply, que exigem adicionalmente umdomain.
curl -X POST https://api.brevo.com/v3/webhooks \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.example/hooks/brevo", "type": "transactional", "events": ["delivered", "hardBounce", "spam", "unsubscribed"], "description": "Deliverability signals" }'Três coisas para acertar. Primeira: uma conta pode ter no máximo 40 webhooks somando todos os tipos, então roteie por evento dentro do seu handler em vez de registrar um endpoint por evento. Segunda: use a flag batched quando esperar volume, já que uma requisição carregando muitos eventos é bem mais barata de processar que muitas requisições. Terceira: proteja o receptor. A Brevo publica suas faixas de IP de envio, e restringir seu endpoint a essas faixas é a abordagem documentada. Adicione seu próprio segredo compartilhado pelo campo headers como segunda camada.
Os handlers precisam ser idempotentes. Trate o id da mensagem mais o tipo de evento mais o timestamp como chave de deduplicação.
Limites de taxa e tratamento de erros
Os limites de taxa da Brevo são por endpoint e por nível de plano, e a distância entre endpoints é enorme.
| Endpoint | Padrão | Professional e Enterprise |
|---|---|---|
POST /v3/smtp/email | 1.000 RPS | 2.000 RPS |
POST /v3/transactionalSMS/send | 150 RPS | 200 RPS |
/v3/contacts/... | 10 RPS, 36.000 RPH | 20 RPS, 72.000 RPH |
POST /v3/events | 10 RPS, 36.000 RPH | mais alto no Enterprise |
GET /v3/smtp/emails | 2 RPS, 7.200 RPH | 3 RPS, 10.800 RPH |
| Todo o resto | 100 RPH | 200 RPH |
Essa última linha é a que dói. O envio é praticamente sem medição, enquanto a gestão de campanhas, as leituras de CRM e a maioria das chamadas administrativas dividem um orçamento de 100 requisições por hora nos planos padrão. Um backfill ingênuo que lê o registro de uma empresa antes de cada escrita esgota uma hora de cota em menos de dois minutos.
Toda resposta traz x-sib-ratelimit-limit, x-sib-ratelimit-remaining e x-sib-ratelimit-reset. Leia esses cabeçalhos no sucesso, não só na falha. Ultrapassar um limite devolve 429, e a resposta correta é esperar o intervalo indicado no cabeçalho de reset e depois aplicar backoff exponencial com jitter.
async function callBrevo(path, init, attempt = 0) { const response = await fetch(`https://api.brevo.com/v3${path}`, { ...init, headers: { "api-key": process.env.BREVO_API_KEY, "Content-Type": "application/json", ...init.headers }, });
if (response.status === 429 && attempt < 5) { const reset = Number(response.headers.get("x-sib-ratelimit-reset") || 1); const backoff = Math.pow(2, attempt) * 250 + Math.random() * 250; await new Promise((r) => setTimeout(r, reset * 1000 + backoff)); return callBrevo(path, init, attempt + 1); }
return response;}Repita em 429 e 5xx. Nunca repita 400 ou 409 às cegas, porque os dois normalmente significam que a requisição está errada, não adiantada, e um 409 em particular pede uma ação diferente, não uma repetição.
Testar sem enviar e-mail
Adicione o cabeçalho X-Sib-Sandbox com o valor drop a um envio transacional. A Brevo valida a requisição, devolve 201 com um messageId, não entrega nada e não grava nenhum log de e-mail.
curl -X POST https://api.brevo.com/v3/smtp/email \ -H "api-key: $BREVO_API_KEY" \ -H "X-Sib-Sandbox: drop" \ -H "Content-Type: application/json" \ -d '{ "sender": { "email": "[email protected]" }, "to": [{ "email": "[email protected]" }], "subject": "Sandbox", "htmlContent": "<p>hi</p>" }'Entenda o que isso prova e o que não prova. O modo sandbox valida apenas o formato da requisição. Ele não diz nada sobre autenticação do remetente, renderização de template ou entregabilidade. Mantenha uma conta Brevo separada para testes de integração de qualquer coisa que toque contatos ou dados de CRM, porque o modo sandbox cobre o envio e não o resto da API.
Limites que moldam o desenho da sua integração
Estas são as restrições que só aparecem quando uma integração roda contra uma conta real em volume. Várias contradizem o que a API diz sobre si mesma. Nenhuma delas é negociável, então a única resposta sensata é desenhar em torno delas.
Empresas exigem domínio, e só uma empresa por domínio
GET /v3/crm/attributes/companies informa todos os atributos como não obrigatórios, e a referência de criação de empresa lista apenas name como mandatório. Na prática, POST /v3/companies sem um atributo domain não vazio devolve 400 com uma mensagem sobre atributos padrão obrigatórios ausentes. Uma string vazia falha do mesmo jeito que omitir o campo.
Pior: a unicidade de domínio é imposta. Uma segunda empresa em um domínio já em uso devolve 409. Para comércio B2B isso é estrutural: subsidiárias que compartilham um mesmo domínio de e-mail de comprador não podem existir todas como empresas separadas na Brevo. Sincronizar um contato também é suficiente para fazer aparecer uma empresa no domínio de e-mail desse contato, então uma criação pode colidir com uma empresa que ninguém criou explicitamente. O handler correto adota a empresa existente no 409 em vez de falhar ou repetir.
Atributos não declarados são descartados em silêncio
Este é o comportamento mais perigoso da plataforma, e a Brevo documenta isso com todas as letras: se um atributo aparece em uma requisição mas não foi previamente definido no schema do objeto, nada acontece. Sem erro, sem criação de atributo, sem aviso.
Uma resposta 2xx, portanto, não é prova de que seus dados chegaram. Leia o schema antes de escrever, descarte qualquer coisa não declarada no seu próprio cliente e recuse rodar uma sincronização cujos atributos não existem, em vez de escrever meio registro por um mês até alguém perceber.
Filtros por atributo são aceitos e ignorados
GET /v3/companies?filters[attributes.domain]=... devolve 200 e ignora o filtro. Dois filtros completamente diferentes devolvem os mesmos registros. Não existe jeito funcional de localizar uma empresa por atributo por essa rota.
Somando ao fato de que a lista sem filtro dá timeout com 504 em contas grandes com qualquer tamanho de página, uma empresa existente pode ser genuinamente impossível de encontrar pelo caminho documentado. A saída é varrer GET /v3/objects/company/records com sort=desc, que é rápido, paginado e devolve atributos, limitado a um número sensato de páginas. Uma empresa que acabou de disparar um 409 quase sempre foi criada momentos antes, então a varredura do mais novo para o mais antigo a encontra rápido.
Um milhão de registros por tipo de objeto, e nada de exclusão em massa
POST /v3/objects/{type}/batch/upsert devolve 400 assim que um tipo de objeto atinge um milhão de registros. Isso bloqueia atualizações tanto quanto criações: endereçar um registro existente pelo próprio id numérico falha igual. O caminho inteiro de escrita de objetos fecha de uma vez.
Voltar a ficar abaixo do teto é lento, porque POST /v3/objects/{type}/batch/delete devolve 403 para os tipos de objeto padrão da Brevo, como company. A única rota é DELETE /v3/companies/{id}, um registro por chamada, a cerca de 156 ms. Limpar 124.000 registros assim levou horas com 20 workers em paralelo. Monitore a contagem de registros em uma rotina agendada em vez de descobrir o teto por meio de uma sincronização quebrada, e roteie atualizações de alto volume por PATCH /v3/companies/{id}, que não tem esse limite.
ext_id é o id da Brevo, não o seu
Em registros de objeto, identifiers.ext_id guarda o próprio id de empresa do CRM da Brevo, uma string no estilo Mongo. Não é uma chave externa livre. Usar ext_id com o identificador da sua plataforma como chave de upsert cria duplicatas em vez de casar registros. Seu id externo pertence a um atributo declarado próprio.
Upserts de objeto são assíncronos, escritas de CRM não
batch/upsert devolve 202 e um processId, e aplica depois. Um id inexistente falha de forma assíncrona e mesmo assim devolve 202 para quem chamou. PATCH /v3/companies/{id} devolve 204 e é aplicado de forma síncrona. Se sua sincronização reporta sucesso, só o caminho síncrono merece essa palavra sem uma leitura de conferência.
Um checklist curto de integração
- Chaves de API separadas por serviço e por ambiente, rotacionadas em mudanças de equipe.
- Todas as escritas passam por um cliente que lê os cabeçalhos de limite de taxa e faz backoff em 429.
- O schema de atributos é verificado na inicialização, e a sincronização se recusa a rodar se seus atributos estiverem faltando.
- 409 na criação de empresa significa adotar, não repetir.
- Caminhos em massa usam a API de objetos para vazão e as rotas de CRM para qualquer coisa que precise ser confirmada.
- Os webhooks são idempotentes, agrupados, restritos por IP e carregam um cabeçalho de segredo compartilhado.
- Sincronizações incrementais de contatos usam
modifiedSince, não varreduras da lista inteira.
Construir e manter essa camada é trabalho de engenharia de verdade: verificação de schema, backoff, lógica de adoção, reconciliação. A Tajo existe para absorver isso, mantendo dados de Shopify e de comércio sincronizados com contatos, empresas e eventos da Brevo sem ninguém escrever à mão a lógica de retentativa e deduplicação. Se em vez disso você vai montar tudo por conta própria, o guia de integração da Brevo percorre as escolhas de modelo de dados que vêm antes do código.
Principais conclusões
- A API é uma única superfície REST em
https://api.brevo.com/v3/, autenticada com um cabeçalhoapi-keyem vez de um token bearer. - Os limites de taxa são muito desiguais: o envio é praticamente sem medição, enquanto a maioria dos outros endpoints divide 100 requisições por hora nos planos padrão.
- Existem SDKs oficiais para sete linguagens, mas a superfície HTTP é simples o bastante para ser chamada direto quando você só precisa de alguns endpoints.
- O modo sandbox valida apenas o formato da requisição, então mantenha uma conta separada para testar qualquer coisa além de envios.
- Uma resposta 2xx não prova que a escrita foi aplicada. Atributos não declarados são descartados em silêncio, e os upserts de objeto são assíncronos.
- Desenhe em torno dos limites fixos: uma empresa por domínio, um milhão de registros por tipo de objeto, nenhuma exclusão em massa para objetos padrão e filtros por atributo que silenciosamente não fazem nada.