API de Brevo: guía práctica para desarrolladores

Guía de la API de Brevo para desarrolladores: autenticación, URL base, contactos, email transaccional, campañas, objetos de CRM, webhooks, límites de tasa y límites reales.

Brevo API
API de Brevo?

Brevo expone una sola API REST que abarca mensajería transaccional, campañas de marketing, datos de contacto y registros de CRM. Conseguir que la primera solicitud devuelva 201 lleva unos dos minutos. Conseguir una integración de producción que no pierda datos en silencio lleva bastante más, porque varias de las restricciones que más importan están sin documentar o contradicen lo que la propia API dice de sí misma.

Esta guía cubre las dos mitades: los endpoints, los SDK y la autenticación que necesitas el primer día, y los límites de plataforma alrededor de los que tienes que diseñar antes de pasar a producción.

Qué cubre la API de Brevo

Todo vive bajo un único host y una única ruta de versión. La documentación para desarrolladores agrupa la superficie en cuatro áreas de producto:

  • Mensajería: email transaccional, SMS y WhatsApp, incluidos los envíos por lotes, la programación y la actividad de los mensajes.
  • Plataforma de marketing: contactos, listas, segmentos y campañas de email.
  • eCommerce: productos, pedidos y seguimiento de eventos de cliente.
  • Conversaciones: el widget de chat y la gestión programática de conversaciones.

Esas áreas comparten una cuenta, una base de datos de contactos y una clave de API. Eso es cómodo y de vez en cuando peligroso: un script escrito pensando en datos de staging está hablando con los mismos contactos a los que envían tus campañas.

Transaccional frente a marketing

Las dos familias se comportan de forma lo bastante distinta como para que confundirlas sea el error de diseño más habitual.

TransaccionalMarketing
Endpoint principalPOST /v3/smtp/emailPOST /v3/emailCampaigns
DestinatariosExplícitos en la solicitudlistIds o segmentIds
DisparadorTu aplicación, en tiempo realProgramado o enviado a demanda
Forma habitual del volumenContinuo, un mensaje cada vezA ráfagas, un envío grande
Postura ante los límites de tasaMuy alta, 1.000 solicitudes por segundo en planes estándarBaja, los endpoints de campaña caen bajo el límite general

Si todavía estás decidiendo si Brevo es la plataforma adecuada, la visión general de la plataforma cubre ese terreno.

Autenticación y gestión de claves

Brevo usa una clave de API simple en una cabecera propia. La cabecera se llama api-key, no Authorization, y no lleva prefijo Bearer. Esto despista a casi todo el mundo que ha usado antes otra API de mensajería.

Terminal window
curl https://api.brevo.com/v3/account \
-H "api-key: $BREVO_API_KEY"

Las claves se generan en la aplicación de Brevo, dentro de los ajustes de la cuenta, en la sección SMTP y API, en la pestaña de claves de API. Dale a cada clave un nombre descriptivo ligado al sistema que la usa. El valor de la clave se muestra exactamente una vez cuando se genera, así que si la pierdes generas una nueva en lugar de recuperar la antigua.

Unas cuantas reglas prácticas:

  • Emite una clave separada por entorno de despliegue y por servicio. Revocar una clave comprometida nunca debería tumbar tres sistemas que no tienen nada que ver.
  • Las claves de API estándar son de ámbito de cuenta. Trata cualquier clave como acceso total a contactos, envíos y datos de CRM.
  • Brevo también admite OAuth 2.0 para aplicaciones que actúan en nombre de otras cuentas de Brevo, descrito junto al flujo de claves en authentication schemes.
  • El servidor MCP que usan los asistentes de AI toma un token aparte y ese sí usa una cabecera bearer. Ese token se genera en la misma pantalla de claves de API, pero no es intercambiable con una clave REST.

URL base, versionado y tu primera escritura

La URL base es https://api.brevo.com/v3/. La versión va en la ruta y no en una cabecera, y v3 es la generación actual. Todas las rutas de esta guía son relativas a esa base.

Una primera escritura es más informativa que una primera lectura, porque ejercita las partes de la cuenta que suelen estar mal configuradas, en particular los remitentes verificados:

Terminal window
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": "Primer envío transaccional",
"htmlContent": "<html><body><p>Funciona.</p></body></html>",
"tags": ["smoke-test"]
}'

Un envío correcto devuelve 201 con un messageId. Un envío programado devuelve 202.

Los endpoints que vas a usar de verdad

Contactos

POST /v3/contacts crea un contacto. El cuerpo acepta email, un mapa attributes para campos personalizados, listIds, ext_id para tu propia clave externa y las dos banderas que más importan en la práctica: updateEnabled, que convierte la llamada en un upsert, y getId, que hace que la respuesta devuelva el id del contacto.

Las lecturas pasan por GET /v3/contacts, que pagina con limit (50 por defecto, 1000 como máximo) y offset, y admite modifiedSince y createdSince en UTC. Las sincronizaciones incrementales deberían apoyarse en modifiedSince en lugar de recorrer la lista completa. Ten en cuenta que el parámetro filter solo admite un operador de igualdad, así que cualquier cosa más expresiva pertenece a un segmento.

Para carga masiva, POST /v3/contacts/import acepta fileUrl, fileBody o jsonBody, apunta a listIds y se ejecuta de forma asíncrona devolviendo un processId. Brevo documenta un máximo de 10 MB por cuerpo y recomienda quedarse cerca de 8 MB porque el parseo infla la carga útil. Facilita notifyUrl para enterarte del resultado en lugar de hacer polling.

Email transaccional

POST /v3/smtp/email es el caballo de batalla. Más allá de sender, to, subject y htmlContent, los campos que merece la pena conocer son:

  • templateId con params, que sustituye el contenido en línea por una plantilla de Brevo y sus sustituciones de variables. Los params de cada versión están limitados a 100 KB y los acumulados a 1000 KB.
  • messageVersions, que envía variantes personalizadas en una sola llamada, con hasta 99 destinatarios por versión.
  • tags, que deberías fijar siempre. Las etiquetas vuelven en los eventos de webhook y son la única forma barata de correlacionar un evento de entrega con la ruta de código que lo produjo.
  • scheduledAt junto con batchId, para envíos futuros que quizá quieras cancelar en grupo.
  • headers, en Title-Case, para cabeceras SMTP personalizadas.

Una sola solicitud acepta como máximo 2.000 destinatarios. Para la diferencia entre este endpoint y el envío de campañas, la guía de email transaccional ofrece la visión de estrategia de mensajería.

Campañas de email

POST /v3/emailCampaigns requiere name y sender, además de exactamente una fuente de contenido: htmlContent (mínimo 10 caracteres, menos de 1 MB), htmlUrl o templateId. La audiencia va en recipients como listIds o segmentIds, y scheduledAt usa el formato UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Las rutas complementarias cubren el envío inmediato, el envío de una prueba, la actualización del estado y la descarga del informe de campaña.

Empresas, negocios y objetos

El CRM de Brevo tiene dos rutas de escritura solapadas, y elegir bien importa.

Las rutas de CRM son POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} y el conjunto equivalente para los negocios. Son síncronas. Un PATCH devuelve 204 en cuanto se aplica el cambio.

La API de objetos es la vía masiva: POST /v3/objects/{object_type}/batch/upsert acepta hasta 1000 registros y 1 MB por solicitud, hasta 500 atributos por registro y hasta 10 registros de asociación por tipo de objeto y registro. Devuelve 202 con un processId, lo que significa aceptado y no aplicado.

Terminal window
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" }
}
]
}'

La guía de Brevo CRM cubre el modelo de objetos desde el lado del operador.

SDK oficiales

Brevo mantiene clientes en la organización de GitHub getbrevo:

LenguajeRepositorio
Node.jsgithub.com/getbrevo/brevo-node
Pythongithub.com/getbrevo/brevo-python
PHPgithub.com/getbrevo/brevo-php
Javagithub.com/getbrevo/brevo-java
C#github.com/getbrevo/brevo-csharp
Gogithub.com/getbrevo/brevo-go
Rubygithub.com/getbrevo/brevo-ruby

El cliente de Node se instala como @getbrevo/brevo:

Terminal window
npm install @getbrevo/brevo
import { BrevoClient } from "@getbrevo/brevo";
const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY });
const result = await brevo.transactionalEmails.sendTransacEmail({
subject: "Pedido confirmado",
htmlContent: "<html><body><p>Gracias por tu pedido.</p></body></html>",
sender: { name: "Acme", email: "[email protected]" },
to: [{ email: "[email protected]", name: "Cliente" }],
tags: ["order-confirmation"],
});
console.log("Message ID:", result.messageId);

El cliente de Python se instala con pip install brevo-python. Si prefieres no arrastrar una dependencia de SDK para dos endpoints, la superficie HTTP en crudo es lo bastante pequeña como para llamarla directamente, lo que además te aísla de los cambios de versión del SDK:

import os
import 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 response

También hay un servidor MCP en https://mcp.brevo.com/v1/brevo/mcp para asistentes de AI, autenticado con un token bearer generado en la misma pantalla de ajustes. Es útil para exploración y preguntas sobre la cuenta, no para rutas de datos en producción.

Webhooks

Los webhooks son la forma de enterarte de lo que pasó después de un envío. POST /v3/webhooks crea uno, con url, events, type y, de forma opcional, channel (email o sms), batched, headers personalizadas y un objeto auth.

Hay tres tipos de webhook con vocabularios de evento distintos:

  • Transaccional: sent, request, delivered, hardBounce, softBounce, blocked, spam, invalid, deferred, click, opened, uniqueOpened, unsubscribed.
  • Marketing: spam, opened, click, hardBounce, softBounce, unsubscribed, listAddition, delivered, contactUpdated, contactDeleted.
  • Entrante: inboundEmailProcessed y reply, que además requieren un domain.
Terminal window
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": "Señales de entregabilidad"
}'

Hay tres cosas que conviene hacer bien. Primero, una cuenta admite como máximo 40 webhooks entre todos los tipos, así que enruta por evento dentro de tu manejador en lugar de registrar un endpoint por evento. Segundo, usa la bandera batched cuando esperes volumen, porque una solicitud que lleva muchos eventos es mucho más barata de procesar que muchas solicitudes. Tercero, protege el receptor: Brevo publica sus rangos de IP de envío, y restringir tu endpoint a esos rangos es el enfoque documentado. Añade tu propio secreto compartido a través del campo headers como segunda capa.

Los manejadores deben ser idempotentes. Trata el id del mensaje más el tipo de evento más la marca temporal como clave de deduplicación.

Límites de tasa y gestión de errores

Los límites de tasa de Brevo son por endpoint y por nivel de plan, y la diferencia entre endpoints es enorme.

EndpointEstándarProfessional y Enterprise
POST /v3/smtp/email1.000 RPS2.000 RPS
POST /v3/transactionalSMS/send150 RPS200 RPS
/v3/contacts/...10 RPS, 36.000 RPH20 RPS, 72.000 RPH
POST /v3/events10 RPS, 36.000 RPHmás alto en Enterprise
GET /v3/smtp/emails2 RPS, 7.200 RPH3 RPS, 10.800 RPH
Todo lo demás100 RPH200 RPH

Esa última fila es la que duele. Enviar es prácticamente ilimitado, mientras que la gestión de campañas, las lecturas de CRM y la mayoría de las llamadas administrativas comparten un presupuesto de 100 solicitudes por hora en los planes estándar. Un backfill ingenuo que lea un registro de empresa antes de cada escritura agotará una hora de cuota en menos de dos minutos.

Cada respuesta lleva x-sib-ratelimit-limit, x-sib-ratelimit-remaining y x-sib-ratelimit-reset. Léelos cuando la llamada va bien, no solo cuando falla. Superar un límite devuelve 429, y la respuesta correcta es esperar el intervalo de la cabecera de reinicio y después aplicar backoff exponencial con 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;
}

Reintenta los 429 y los 5xx. Nunca reintentes un 400 o un 409 a ciegas, porque los dos suelen significar que la solicitud está mal y no que llega pronto, y un 409 en particular necesita una acción distinta y no una repetición.

Probar sin enviar correo

Añade la cabecera X-Sib-Sandbox con el valor drop a un envío transaccional. Brevo valida la solicitud, devuelve 201 con un messageId, no entrega nada y no escribe ningún registro de email.

Terminal window
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>hola</p>" }'

Entiende qué demuestra esto y qué no. El modo sandbox valida solo el formato de la solicitud. No dice nada sobre la autenticación del remitente, el renderizado de plantillas ni la entregabilidad. Mantén una cuenta de Brevo aparte para las pruebas de integración de todo lo que toque contactos o datos de CRM, porque el modo sandbox cubre el envío y no el resto de la API.

Límites que condicionan el diseño de tu integración

Estas son las restricciones que solo aparecen cuando una integración se ejecuta contra una cuenta real con volumen. Varias contradicen lo que la API dice de sí misma. Ninguna es negociable, así que la única respuesta sensata es diseñar a su alrededor.

Las empresas necesitan un dominio, y solo una empresa por dominio

GET /v3/crm/attributes/companies informa de que ningún atributo es obligatorio, y la referencia de creación de empresa lista solo name como obligatorio. En la práctica, POST /v3/companies sin un atributo domain no vacío devuelve 400 con un mensaje sobre atributos por defecto obligatorios que faltan. Una cadena vacía falla igual que omitirlo.

Peor aún, la unicidad de dominio se aplica de verdad. Una segunda empresa en un dominio ya en uso devuelve 409. Para el comercio B2B esto es estructural: las filiales que comparten un dominio de correo de comprador no pueden existir todas como empresas separadas en Brevo. Sincronizar un contacto también basta para que aparezca una empresa con el dominio de correo de ese contacto, así que una creación puede chocar con una empresa que nadie creó de forma explícita. El manejador correcto adopta la empresa existente ante un 409 en lugar de fallar o reintentar.

Los atributos no declarados se descartan en silencio

Este es el comportamiento más peligroso de la plataforma, y Brevo lo documenta con claridad: si un atributo aparece en una solicitud pero no estaba definido antes en el esquema del objeto, no pasa nada. Ni error, ni creación del atributo, ni aviso.

Por tanto, una respuesta 2xx no es prueba de que tus datos hayan aterrizado. Lee el esquema antes de escribir, descarta en tu propio cliente todo lo que no esté declarado y niégate a ejecutar una sincronización cuyos atributos no existen, en lugar de escribir medio registro durante un mes hasta que alguien se dé cuenta.

Los filtros por atributo se aceptan y se ignoran

GET /v3/companies?filters[attributes.domain]=... devuelve 200 e ignora el filtro. Dos filtros totalmente distintos devuelven los mismos registros. No hay forma de buscar una empresa por atributo a través de esa ruta.

Sumado a que el listado sin filtrar caduca con 504 en cuentas grandes con cualquier tamaño de página, una empresa existente puede resultar literalmente imposible de encontrar por la vía documentada. La solución es recorrer GET /v3/objects/company/records con sort=desc, que es rápido, paginado y devuelve atributos, acotado a un número razonable de páginas. Una empresa que acaba de provocar un 409 casi siempre se creó momentos antes, así que un recorrido de más nuevo a más antiguo la encuentra rápido.

Un millón de registros por tipo de objeto, y sin borrado masivo

POST /v3/objects/{type}/batch/upsert devuelve 400 en cuanto un tipo de objeto llega al millón de registros. Bloquea las actualizaciones además de las creaciones: dirigirse a un registro existente por su propio id numérico falla igual. Toda la ruta de escritura de objetos se cierra de golpe.

Volver por debajo del techo es lento, porque POST /v3/objects/{type}/batch/delete devuelve 403 para los tipos de objeto estándar de Brevo como company. La única vía es DELETE /v3/companies/{id}, un registro por llamada a unos 156 ms. Limpiar 124.000 registros así llevó horas con 20 trabajadores en paralelo. Vigila el recuento de registros de forma programada en lugar de descubrir el techo con una sincronización fallida, y enruta las actualizaciones de alto volumen por PATCH /v3/companies/{id}, que no tiene ese límite.

ext_id es el id de Brevo, no el tuyo

En los registros de objeto, identifiers.ext_id contiene el propio id de empresa del CRM de Brevo, una cadena al estilo de Mongo. No es una clave externa libre. Basar un upsert en ext_id con el identificador de tu plataforma crea duplicados en lugar de emparejar. Tu id externo va en un atributo declarado propio.

Los upserts de objetos son asíncronos, las escrituras de CRM no

batch/upsert devuelve 202 y un processId, y se aplica más tarde. Un id inexistente falla de forma asíncrona y aun así devuelve 202 a quien llama. PATCH /v3/companies/{id} devuelve 204 y se aplica de forma síncrona. Si tu sincronización informa de éxito, solo la vía síncrona se gana esa palabra sin una lectura de confirmación.

Una lista de comprobación breve para la integración

  • Claves de API separadas por servicio y por entorno, rotadas cuando cambia el equipo.
  • Todas las escrituras pasan por un único cliente que lee las cabeceras de límite de tasa y aplica backoff ante un 429.
  • El esquema de atributos se verifica al arrancar, y la sincronización se niega a ejecutarse si faltan sus atributos.
  • Un 409 al crear una empresa significa adoptar, no reintentar.
  • Las vías masivas usan la API de objetos para el rendimiento y las rutas de CRM para todo lo que deba confirmarse.
  • Los webhooks son idempotentes, agrupados, restringidos por IP y llevan una cabecera con un secreto compartido.
  • Las sincronizaciones incrementales de contactos usan modifiedSince, no recorridos de la lista completa.

Construir y mantener esta capa es trabajo de ingeniería de verdad: verificación de esquema, backoff, lógica de adopción, reconciliación. Tajo existe para absorberlo, manteniendo los datos de Shopify y de comercio sincronizados con los contactos, empresas y eventos de Brevo sin que nadie escriba a mano la lógica de reintentos y deduplicación. Si vas a cablearlo tú, la guía de integración de Brevo recorre las decisiones de modelo de datos que van antes del código.

Conclusiones clave

  • La API es una sola superficie REST en https://api.brevo.com/v3/, autenticada con una cabecera api-key en lugar de un token bearer.
  • Los límites de tasa son tremendamente desiguales: enviar es prácticamente ilimitado, mientras que la mayoría de los demás endpoints comparten 100 solicitudes por hora en los planes estándar.
  • Hay SDK oficiales para siete lenguajes, pero la superficie HTTP es lo bastante simple como para llamarla directamente cuando solo necesitas unos pocos endpoints.
  • El modo sandbox valida solo el formato de la solicitud, así que mantén una cuenta aparte para probar cualquier cosa que vaya más allá de los envíos.
  • Una respuesta 2xx no demuestra que una escritura se haya aplicado. Los atributos no declarados se descartan en silencio y los upserts de objetos son asíncronos.
  • Diseña alrededor de los límites fijos: una empresa por dominio, un millón de registros por tipo de objeto, sin borrado masivo para los objetos estándar y filtros por atributo que no hacen nada.

Preguntas frecuentes

¿Cuál es la URL base de la API de Brevo?
Todas las llamadas REST de Brevo van a https://api.brevo.com/v3/. La versión forma parte de la ruta y v3 es la generación actual, así que un endpoint como el envío transaccional es la ruta completa https://api.brevo.com/v3/smtp/email.
¿Cómo te autenticas en la API de Brevo?
Envía tu clave en una cabecera HTTP llamada api-key. Brevo no usa una cabecera Authorization ni un prefijo Bearer para las claves de API estándar. Las claves se generan en los ajustes de la cuenta, en la sección SMTP y API, y el valor se muestra una sola vez.
¿Cuáles son los límites de tasa de la API de Brevo?
Los límites son por endpoint y por nivel de plan. En cuentas estándar, el endpoint de envío transaccional permite 1.000 solicitudes por segundo, los endpoints de contactos permiten 10 por segundo y todos los demás están limitados a 100 solicitudes por hora. Los planes Professional y Enterprise tienen niveles más altos. Superar un límite devuelve 429.
¿Brevo tiene SDK oficiales?
Sí. Brevo publica clientes para Node.js, Python, PHP, Java, C#, Go y Ruby en la organización de GitHub getbrevo. El paquete de Node es @getbrevo/brevo en npm y el de Python es brevo-python en PyPI.
¿Cómo pruebo la API de Brevo sin enviar correos reales?
Añade la cabecera X-Sib-Sandbox con el valor drop a un envío transaccional. Brevo valida la solicitud, devuelve 201 con un messageId, no envía nada y no escribe ningún registro de email. Solo comprueba el formato de la solicitud, no la entregabilidad.
¿Qué eventos de webhook admite Brevo?
Los webhooks transaccionales cubren sent, request, delivered, hardBounce, softBounce, blocked, spam, invalid, deferred, click, opened, uniqueOpened y unsubscribed. Los de marketing añaden listAddition, contactUpdated y contactDeleted. Los entrantes cubren inboundEmailProcessed y reply. Una cuenta admite un máximo de 40 webhooks en total.
¿Puedo crear dos empresas con el mismo dominio en Brevo?
No. Brevo exige que el dominio sea único en los registros de empresa y devuelve 409 con un error de unicidad de dominio si lo intentas. El comportamiento correcto de una integración es adoptar la empresa existente en lugar de reintentar la creación.
¿Por qué una escritura en la API de Brevo devuelve éxito pero no cambia nada?
Las escrituras en objetos y en el CRM descartan en silencio los atributos que no están declarados en el esquema del objeto. Brevo documenta este comportamiento de forma explícita: no se lanza ningún error y no se crea ningún atributo. Lee el esquema antes de escribir y verifica la escritura en lugar de fiarte de un estado 2xx.
¿Existe un borrado masivo en la API de Brevo?
No para los tipos de objeto estándar de Brevo como company. La ruta de borrado por lotes devuelve 403 para ellos, así que la limpieza se hace registro a registro con DELETE /v3/companies/{id}. Planifica la limpieza masiva en horas, no en minutos.

Solicita acceso anticipado

Indica tu nombre y un email o número de teléfono. Nos pondremos en contacto contigo para darte los detalles de acceso a Tajo.

detección automática
Obtener Brevo