Brevo API: en praktisk guide for utviklere
Brevo API-guide for utviklere: autentisering, base-URL, kontakter, transaksjonell e-post, kampanjer, CRM-objekter, webhooks, rategrenser og grensene du møter i praksis.
Brevo eksponerer ett REST-API som spenner over transaksjonell meldingsutsending, markedsføringskampanjer, kontaktdata og CRM-oppføringer. Å få den første forespørselen til å returnere 201 tar rundt to minutter. Å få en produksjonsintegrasjon som ikke mister data i stillhet tar betydelig lengre tid, fordi flere av de viktigste begrensningene enten er udokumenterte eller motsier det API-et sier om seg selv.
Denne guiden dekker begge halvdelene: endepunktene, SDK-ene og autentiseringen du trenger dag én, og plattformgrensene du må designe rundt før du går i produksjon.
Hva Brevo API dekker
Alt ligger under én host og én versjonssti. Utviklerdokumentasjonen grupperer flaten i fire produktområder:
- Meldinger: transaksjonell e-post, SMS og WhatsApp, inkludert batch-sendinger, planlegging og meldingsaktivitet.
- Markedsføringsplattform: kontakter, lister, segmenter og e-postkampanjer.
- eHandel: produkter, ordrer og sporing av kundehendelser.
- Samtaler: chat-widgeten og programmatisk håndtering av samtaler.
Disse områdene deler én konto, én kontaktdatabase og én API-nøkkel. Det er praktisk og av og til farlig: et skript skrevet mot en tenkt testversjon av dataene snakker med de samme kontaktene som kampanjene dine sender til.
Transaksjonell mot markedsføring
De to familiene oppfører seg ulikt nok til at det å blande dem er den vanligste designfeilen.
| Transaksjonell | Markedsføring | |
|---|---|---|
| Primært endepunkt | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Adressering | Eksplisitte mottakere i forespørselen | listIds eller segmentIds |
| Utløser | Applikasjonen din, i sanntid | Planlagt eller sendt ved behov |
| Typisk volumform | Kontinuerlig, én melding om gangen | Støtvis, én stor sending |
| Rategrense | Svært høy, 1 000 forespørsler per sekund på standardplaner | Lav, kampanjeendepunkter faller under den generelle grensen |
Er du fortsatt usikker på om Brevo er riktig plattform i det hele tatt, dekker plattformoversikten det terrenget.
Autentisering og nøkkelhåndtering
Brevo bruker en ren API-nøkkel i en egendefinert header. Headeren heter api-key, ikke Authorization, og det finnes ingen Bearer-prefiks. Dette snubler nesten alle som har brukt et annet meldings-API først.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Nøkler genereres i Brevo-appen under kontoinnstillinger, i seksjonen SMTP og API, på fanen API-nøkler. Gi hver nøkkel et beskrivende navn knyttet til systemet som bruker den. Nøkkelverdien vises nøyaktig én gang når den genereres, så mister du den, genererer du en ny i stedet for å hente frem den gamle.
Noen praktiske regler:
- Utsted en egen nøkkel per miljø og per tjeneste. Å trekke tilbake en kompromittert nøkkel skal aldri ta ned tre urelaterte systemer.
- Vanlige API-nøkler gjelder hele kontoen. Behandle enhver nøkkel som full tilgang til kontakter, sending og CRM-data.
- Brevo støtter også OAuth 2.0 for applikasjoner som handler på vegne av andre Brevo-kontoer, beskrevet sammen med nøkkelflyten i authentication schemes.
- MCP-serveren som brukes av AI-assistenter, tar et eget token og bruker faktisk en bearer-header. Det tokenet genereres i samme skjermbilde for API-nøkler, men kan ikke brukes om hverandre med en REST-nøkkel.
Base-URL, versjonering og din første skriving
Base-URL-en er https://api.brevo.com/v3/. Versjonen ligger i stien og ikke i en header, og v3 er dagens generasjon. Alle stier i denne guiden er relative til den basen.
En første skriving sier mer enn en første lesing, fordi den tar i bruk de delene av kontoen som oftest er feilkonfigurert (verifiserte avsendere, spesielt):
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"] }'En vellykket sending returnerer 201 med en messageId. En planlagt sending returnerer 202.
Endepunktene du faktisk kommer til å bruke
Kontakter
POST /v3/contacts oppretter en kontakt. Kroppen tar email, et attributes-kart for egendefinerte felter, listIds, ext_id for din egen eksterne nøkkel, og de to flaggene som betyr mest i praksis: updateEnabled, som gjør kallet til en upsert, og getId, som gjør at svaret returnerer kontakt-ID-en.
Lesing går gjennom GET /v3/contacts, som pagineres med limit (standard 50, maksimum 1000) og offset, og støtter modifiedSince og createdSince i UTC. Inkrementelle synkroniseringer bør lene seg på modifiedSince i stedet for å gå gjennom hele listen. Merk at parameteren filter bare støtter en likhetsoperator, så alt mer uttrykksfullt hører hjemme i et segment.
For masseinnlasting tar POST /v3/contacts/import imot fileUrl, fileBody eller jsonBody, retter seg mot listIds og kjører asynkront med en processId i svaret. Brevo dokumenterer en grense på 10 MB kropp og anbefaler å holde seg nær 8 MB, fordi parsingen blåser opp nyttelasten. Oppgi notifyUrl slik at du får vite resultatet i stedet for å polle.
Transaksjonell e-post
POST /v3/smtp/email er arbeidshesten. Utover sender, to, subject og htmlContent er dette feltene som er verdt å kjenne:
templateIdmedparams, som erstatter inline innhold med en Brevo-mal og variabelinnsettingene den bruker. Enkeltversjoners params er begrenset til 100 KB, samlet params til 1000 KB.messageVersions, som sender personaliserte varianter i ett kall, med opptil 99 mottakere per versjon.tags, som du alltid bør sette. Tagger kommer tilbake på webhook-hendelser, og de er den eneste billige måten å knytte en leveringshendelse til kodeveien som produserte den.scheduledAtsammen medbatchId, for fremtidige sendinger du kanskje vil kansellere som gruppe.headers, i Title-Case, for egendefinerte SMTP-headere.
Én enkelt forespørsel tar imot maksimalt 2 000 mottakere. For forskjellen mellom dette endepunktet og kampanjesending har guiden til transaksjonell e-post det strategiske perspektivet.
E-postkampanjer
POST /v3/emailCampaigns krever name og sender, pluss nøyaktig én innholdskilde: htmlContent (minimum 10 tegn, under 1 MB), htmlUrl eller templateId. Målgruppen ligger i recipients som listIds eller segmentIds, og scheduledAt bruker UTC-formatet YYYY-MM-DDTHH:mm:ss.SSSZ. Tilhørende ruter dekker umiddelbar sending, testsending, statusoppdatering og uthenting av kampanjerapporten.
Bedrifter, deals og objekter
Brevos CRM har to overlappende skriveveier, og det å velge riktig betyr noe.
CRM-rutene er POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} og tilsvarende sett for deals. Disse er synkrone. En PATCH returnerer 204 så snart endringen er anvendt.
Objekt-API-et er masseveien: POST /v3/objects/{object_type}/batch/upsert tar opptil 1000 oppføringer og 1 MB per forespørsel, opptil 500 attributter per oppføring og opptil 10 assosiasjonsoppføringer per objekttype per oppføring. Det returnerer 202 med en processId, altså mottatt og ikke anvendt.
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" } } ] }'Brevo CRM-guiden dekker objektmodellen fra operatørens side.
Offisielle SDK-er
Brevo vedlikeholder klienter under GitHub-organisasjonen getbrevo:
| Språk | Repositorium |
|---|---|
| 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 |
Node-klienten installeres som @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);Python-klienten installeres med pip install brevo-python. Vil du heller slippe en SDK-avhengighet for to endepunkter, er den rå HTTP-flaten liten nok til å kalle direkte, noe som også skjermer deg mot stadige SDK-versjoner:
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 responseDet finnes også en MCP-server på https://mcp.brevo.com/v1/brevo/mcp for AI-assistenter, autentisert med et bearer-token generert i samme innstillingsskjerm. Den er nyttig for utforsking og kontospørsmål, ikke for datastrømmer i produksjon.
Webhooks
Webhooks er måten du får vite hva som skjedde etter en sending. POST /v3/webhooks oppretter én, med url, events, type og eventuelt channel (email eller sms), batched, egendefinerte headers og et auth-objekt.
Det finnes tre webhook-typer med hvert sitt hendelsesvokabular:
- Transaksjonell:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Markedsføring:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Innkommende:
inboundEmailProcessedogreply, som i tillegg krever etdomain.
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" }'Tre ting må sitte. For det første kan en konto ha maksimalt 40 webhooks på tvers av alle typer, så rut på hendelse inne i behandleren din i stedet for å registrere ett endepunkt per hendelse. For det andre: bruk batched-flagget når du venter volum, siden én forespørsel som bærer mange hendelser er langt billigere å behandle enn mange forespørsler. For det tredje: beskytt mottakeren. Brevo publiserer sine avsender-IP-områder, og å begrense endepunktet ditt til de områdene er den dokumenterte fremgangsmåten. Legg til din egen delte hemmelighet gjennom headers-feltet som et ekstra lag.
Behandlere må være idempotente. Bruk meldings-ID pluss hendelsestype pluss tidsstempel som dedupliseringsnøkkel.
Rategrenser og feilhåndtering
Brevos rategrenser er satt per endepunkt og per plannivå, og spennet mellom endepunktene er enormt.
| Endepunkt | Standard | Professional og 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 | høyere på Enterprise |
GET /v3/smtp/emails | 2 RPS, 7 200 RPH | 3 RPS, 10 800 RPH |
| Alt annet | 100 RPH | 200 RPH |
Den siste raden er den som svir. Sending er i praksis umålt, mens kampanjehåndtering, CRM-lesinger og de fleste administrative kall deler et budsjett på 100 forespørsler per time på standardplaner. En naiv tilbakefylling som leser en bedriftsoppføring før hver skriving, tømmer en times kvote på under to minutter.
Hvert svar bærer x-sib-ratelimit-limit, x-sib-ratelimit-remaining og x-sib-ratelimit-reset. Les dem ved suksess, ikke bare ved feil. Overskrider du en grense, får du 429, og riktig respons er å vente intervallet i reset-headeren og deretter bruke eksponentiell backoff med 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;}Prøv 429 og 5xx på nytt. Aldri prøv 400 eller 409 på nytt i blinde, fordi begge som regel betyr at forespørselen er feil og ikke for tidlig, og en 409 krever spesielt en annen handling i stedet for en gjentakelse.
Testing uten å sende e-post
Legg til headeren X-Sib-Sandbox med verdien drop på en transaksjonell sending. Brevo validerer forespørselen, returnerer 201 med en messageId, leverer ingenting og skriver ingen e-postlogg.
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>" }'Forstå hva dette beviser og ikke beviser. Sandkassemodus validerer bare formatet på forespørselen. Den sier ingenting om avsenderautentisering, malgjengivelse eller leveringsevne. Ha en separat Brevo-konto for integrasjonstesting av alt som berører kontakter eller CRM-data, fordi sandkassemodus dekker sending og ikke resten av API-et.
Grenser som former integrasjonsdesignet ditt
Dette er begrensningene som først dukker opp når en integrasjon kjører mot en ekte konto med volum. Flere av dem motsier det API-et sier om seg selv. Ingen av dem er forhandlingsbare, så den eneste fornuftige responsen er å designe rundt dem.
Bedrifter krever et domene, og bare én bedrift per domene
GET /v3/crm/attributes/companies rapporterer hver attributt som ikke påkrevd, og referansen for å opprette en bedrift lister bare name som obligatorisk. I praksis returnerer POST /v3/companies uten en ikke-tom domain-attributt 400 med en melding om manglende obligatoriske standardattributter. En tom streng feiler på samme måte som å utelate den.
Verre: domeneunikhet håndheves. En bedrift nummer to på et domene som allerede er i bruk, returnerer 409. For B2B-handel er dette strukturelt: datterselskaper som deler ett innkjøpsdomene i e-postadressene, kan ikke alle eksistere som separate bedrifter i Brevo. Å synkronisere en kontakt er også nok til at en bedrift dukker opp på den kontaktens e-postdomene, så en opprettelse kan kollidere med en bedrift ingen eksplisitt opprettet. Riktig behandler overtar den eksisterende bedriften ved 409 i stedet for å feile eller prøve på nytt.
Udeklarerte attributter forkastes i stillhet
Dette er den farligste oppførselen i plattformen, og Brevo dokumenterer den rett ut: dersom en attributt finnes i en forespørsel, men ikke tidligere er definert i objektskjemaet, skjer ingenting. Ingen feil, ingen opprettelse av attributt, ingen advarsel.
Et 2xx-svar er derfor ikke bevis på at dataene dine landet. Les skjemaet før du skriver, kast alt udeklarert i din egen klient, og nekt å kjøre en synkronisering der attributtene ikke finnes, i stedet for å skrive en halv oppføring i en måned før noen oppdager det.
Attributtfiltre tas imot og ignoreres
GET /v3/companies?filters[attributes.domain]=... returnerer 200 og ignorerer filteret. To helt ulike filtre returnerer de samme oppføringene. Det finnes ingen fungerende måte å slå opp en bedrift på attributt gjennom den ruten.
Kombinert med at den ufiltrerte listen får 504-timeout på store kontoer uansett sidestørrelse, kan en eksisterende bedrift være reelt umulig å finne gjennom den dokumenterte veien. Løsningen er å skanne GET /v3/objects/company/records med sort=desc, som er raskt, paginert og returnerer attributter, avgrenset til et fornuftig antall sider. En bedrift som nettopp utløste en 409, ble nesten alltid opprettet få øyeblikk tidligere, så skanning med nyeste først finner den kjapt.
Én million oppføringer per objekttype, og ingen masseslett
POST /v3/objects/{type}/batch/upsert returnerer 400 så snart en objekttype rommer én million oppføringer. Det blokkerer oppdateringer så vel som opprettelser: å adressere en eksisterende oppføring med dens egen numeriske ID feiler på nøyaktig samme måte. Hele skriveveien for objekter lukkes på én gang.
Å komme seg under taket igjen går sakte, fordi POST /v3/objects/{type}/batch/delete returnerer 403 for Brevos standard objekttyper som company. Eneste vei er DELETE /v3/companies/{id}, én oppføring per kall på rundt 156 ms. Å tømme 124 000 oppføringer på den måten tok timer med 20 parallelle arbeidere. Overvåk antall oppføringer på et fast intervall i stedet for å oppdage taket gjennom en mislykket synkronisering, og rut oppdateringer med høyt volum gjennom PATCH /v3/companies/{id}, som ikke har en slik grense.
ext_id er Brevos ID, ikke din
På objektoppføringer holder identifiers.ext_id Brevos egen CRM-bedrifts-ID, en streng i Mongo-stil. Det er ikke en fri ekstern nøkkel. Å nøkle en upsert på ext_id satt til plattformens egen identifikator lager duplikater i stedet for treff. Din eksterne ID hører hjemme i en egen deklarert attributt.
Objekt-upserts er asynkrone, CRM-skrivinger er det ikke
batch/upsert returnerer 202 og en processId, og anvendes senere. En ID som ikke finnes, feiler asynkront og returnerer likevel 202 til den som kalte. PATCH /v3/companies/{id} returnerer 204 og anvendes synkront. Hvis synkroniseringen din rapporterer suksess, er det bare den synkrone veien som fortjener ordet uten en oppfølgende lesing.
En kort integrasjonssjekkliste
- Separate API-nøkler per tjeneste og per miljø, rotert ved personalendringer.
- All skriving går gjennom én klient som leser rategrense-headere og backer av på 429.
- Attributtskjemaet verifiseres ved oppstart, og synkroniseringen nekter å kjøre hvis attributtene mangler.
- 409 ved bedriftsopprettelse betyr overta, ikke prøve på nytt.
- Masseveier bruker objekt-API-et for gjennomstrømning og CRM-rutene for alt som må bekreftes.
- Webhooks er idempotente, batchede, IP-begrensede og bærer en header med delt hemmelighet.
- Inkrementelle kontaktsynkroniseringer bruker
modifiedSince, ikke gjennomgang av hele listen.
Å bygge og vedlikeholde dette laget er reelt ingeniørarbeid: skjemaverifisering, backoff, overtakelseslogikk, avstemming. Tajo finnes for å absorbere det, og holder Shopify- og handelsdata i synk med Brevo-kontakter, bedrifter og hendelser uten at noen håndskriver logikken for gjenforsøk og deduplisering. Kobler du det selv i stedet, går Brevo-integrasjonsguiden gjennom valgene i datamodellen som kommer før koden.
Viktigste punkter
- API-et er én REST-flate på
https://api.brevo.com/v3/, autentisert med enapi-key-header i stedet for et bearer-token. - Rategrensene er svært ujevne: sending er i praksis umålt, mens de fleste andre endepunkter deler 100 forespørsler per time på standardplaner.
- Det finnes offisielle SDK-er for sju språk, men HTTP-flaten er enkel nok til å kalle direkte når du bare trenger noen få endepunkter.
- Sandkassemodus validerer bare formatet på forespørselen, så ha en separat konto for testing av alt utover sending.
- Et 2xx-svar beviser ikke at en skriving ble anvendt. Udeklarerte attributter droppes i stillhet, og objekt-upserts er asynkrone.
- Design rundt de faste grensene: én bedrift per domene, én million oppføringer per objekttype, ingen masseslett for standardobjekter, og attributtfiltre som stille gjør ingenting.