Så importerar du CSV- eller Excel-kontakter till Brevo med ett skript (Python, Node.js, cURL)
Massimportera kontakter från en CSV- eller Excel-fil till Brevo med API:t import-contacts. Innehåller färdiga skript i Python och Node.js, en cURL-rad, felhantering och tips för filer större än 10 MB.
Om du någon gång har behövt skicka in några tusen kontakter från ett kalkylblad till Brevo så vet du att den manuella vägen i gränssnittet snabbt blir jobbig: välj fil, mappa kolumner, välj lista, vänta, upprepa. Ett skript gör samma jobb på några sekunder och, viktigare, du kan köra det igen enligt schema, efter en databasexport eller varje gång ditt CRM spottar ut en ny CSV.
Den här guiden går igenom API-endpointen Brevo erbjuder för exakt det här, med kompletta skript i Python, Node.js och en cURL-rad. Allt i guiden går mot POST /v3/contacts/import.
Endpointen i korthet
POST https://api.brevo.com/v3/contacts/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "listIds": [42], "updateExistingContacts": true, "emailBlacklist": false, "smsBlacklist": false}Svaret kommer snabbt:
{ "processId": 78 }Det är ett 202 Accepted. Brevo har tagit emot importen och bearbetar den i bakgrunden. processId är ditt spårnings-id om du vill polla efter färdigställning eller sätta upp en notifyUrl-webhook.
Några saker värda att veta innan du börjar koda:
- Body kan vara CSV (
fileBody), JSON (jsonBody) eller en extern URL (fileUrl). Välj en. CSV-formen använder semikolon (;) som kolumnavskiljare, inte komma. Det är en vanlig fallgrop. fileBodyochjsonBodyär begränsade till 10 MB. Brevo rekommenderar att du håller dig runt 8 MB för säkerhets skull. För något större laddar du upp filen till S3, GCS eller valfri HTTPS-host och skickarfileUrli stället.- Anpassade attribut som inte finns på ditt konto ignoreras tyst. Skapa dem i Brevos gränssnitt (eller via Attributes API) innan du importerar rader som använder dem, annars försvinner data bara.
updateExistingContactshar true som standard. Om du sätter det tillfalsehoppar Brevo över kontakter vars e-post redan finns, användbart för jobb som “lägg bara till nya”.emptyContactsAttributesstyr om tomma celler i din CSV ska radera befintliga värden. Standardfalse(tomma celler ignoreras). Sätt tilltrueom din CSV är källan till sanning och du vill att tomma värden ska rensa bort gammal data.
Skaffa en API-nyckel
Logga in på Brevo, gå till Settings → SMTP & API → API Keys och skapa en ny nyckel. Den ser ut som xkeysib-.... Du skickar den som HTTP-headern api-key i varje anrop. Behandla den som ett lösenord, den har full läs- och skrivåtkomst på ditt konto.
Ett rent mönster: lägg den i en miljövariabel så att den aldrig hamnar i ditt källträd.
export BREVO_API_KEY="xkeysib-..."Python: läs en CSV och skicka den till Brevo
Det här är det enklaste tänkbara skriptet: läs en lokal CSV med standardbiblioteket och skicka body direkt till Brevo. Inget tredjeparts-SDK behövs utöver requests.
import csvimport ioimport osimport sysimport requests
API_KEY = os.environ["BREVO_API_KEY"]LIST_ID = 42 # the Brevo list to add contacts toINPUT_FILE = sys.argv[1] if len(sys.argv) > 1 else "contacts.csv"
# Brevo wants semicolon-separated CSV. If your file uses commas (most do),# convert it on the fly so you don't have to edit the source spreadsheet.def to_brevo_csv(path: str) -> str: with open(path, newline="", encoding="utf-8") as f: reader = csv.reader(f) # default: comma-separated out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in reader: writer.writerow(row) return out.getvalue()
body = { "fileBody": to_brevo_csv(INPUT_FILE), "listIds": [LIST_ID], "updateExistingContacts": True, "emptyContactsAttributes": False, "emailBlacklist": False, "smsBlacklist": False,}
resp = requests.post( "https://api.brevo.com/v3/contacts/import", json=body, headers={"api-key": API_KEY, "Content-Type": "application/json"}, timeout=60,)
if resp.status_code != 202: print(f"Import failed: {resp.status_code} {resp.text}") sys.exit(1)
process_id = resp.json()["processId"]print(f"Import accepted. Process ID: {process_id}")Kör det:
python import_csv_to_brevo.py contacts.csvFörsta raden i din CSV är rubriken. Kolumnnamn mappas till Brevo-attribut via versaler, exakt matchning: EMAIL, FIRSTNAME, LASTNAME, plus eventuella anpassade attribut du har definierat. EMAIL är obligatoriskt.
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYDet är hela flödet. Skriptet returnerar inom en sekund. Själva importen körs på serversidan och tar allt från några sekunder till några minuter beroende på volym.
Excel-filer: tre rimliga vägar
Brevos import-endpoint läser inte .xlsx direkt, så du har tre verkliga alternativ beroende på var arbetet behöver bo:
- Kör ett VBA-makro inuti arbetsboken. Ett-klicks-synk från Excel självt, inget externt skript. Det är rätt svar när filen ligger på en dator och ditt team vill ha en knapp på arket. Komplett kod i guiden för Excel-till-Brevo VBA-makro.
- Office Scripts + Power Automate. Om arbetsboken ligger i OneDrive/SharePoint och du vill ha schemalagd synk utan tillsyn. Tas också upp i Excel-guiden.
- Konvertera
.xlsxtill CSV från ett skript. Det här är vad resten av avsnittet handlar om. Bäst om du redan kör Python eller Node på en server och bara behöver dra in en arbetsbok en gång om dagen.
För alternativ 3 räcker pandas en rad:
# pip install pandas openpyxlimport pandas as pd
def xlsx_to_brevo_csv(path: str, sheet_name: str | int = 0) -> str: df = pd.read_excel(path, sheet_name=sheet_name) return df.to_csv(sep=";", index=False)
body["fileBody"] = xlsx_to_brevo_csv("contacts.xlsx")Om du inte vill ha pandas som beroende fungerar openpyxl ensamt:
from openpyxl import load_workbookimport csv, io
def xlsx_to_brevo_csv(path: str) -> str: wb = load_workbook(path, read_only=True) ws = wb.active out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in ws.iter_rows(values_only=True): writer.writerow(["" if v is None else v for v in row]) return out.getvalue()Node.js: samma jobb, officiellt SDK
Brevo publicerar @getbrevo/brevo för Node. Det hanterar autentisering, retries och den typade request-formen:
// npm install @getbrevo/brevoimport fs from 'node:fs/promises';import { BrevoClient } from '@getbrevo/brevo';
const API_KEY = process.env.BREVO_API_KEY;const LIST_ID = 42;const INPUT_FILE = process.argv[2] ?? 'contacts.csv';
const client = new BrevoClient({ apiKey: API_KEY });
// Read the CSV and convert commas to semicolons if neededconst raw = await fs.readFile(INPUT_FILE, 'utf-8');const csv = raw.includes(';') ? raw : raw.replace(/,/g, ';');
const result = await client.contacts.importContacts({ fileBody: csv, listIds: [LIST_ID], updateExistingContacts: true, emptyContactsAttributes: false,});
console.log(`Import accepted. Process ID: ${result.processId}`);Eller, om du inte vill ha SDK:t, vanlig fetch fungerar likadant:
const resp = await fetch('https://api.brevo.com/v3/contacts/import', { method: 'POST', headers: { 'api-key': process.env.BREVO_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ fileBody: csv, listIds: [42], updateExistingContacts: true, }),});
if (resp.status !== 202) { throw new Error(`Import failed: ${resp.status} ${await resp.text()}`);}
const { processId } = await resp.json();console.log(`Process ID: ${processId}`);cURL: en rad för ad hoc-importer
När du bara vill testa endpointen eller skicka in en liten fil för hand:
# Convert commas to semicolons, then send the file inlinecsv=$(sed 's/,/;/g' contacts.csv | jq -Rs .)
curl -X POST https://api.brevo.com/v3/contacts/import \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"fileBody\": $csv, \"listIds\": [42], \"updateExistingContacts\": true}"jq -Rs . slurpar hela filen och JSON-escapar den, så slipper du escapa radbrytningar och citattecken manuellt.
Filer större än 10 MB: använd fileUrl
fileBody är begränsad till 10 MB. Om din kontaktdump är större hostar du filen på en URL Brevo kan hämta och skickar den:
body = { "fileUrl": "https://files.example.com/contacts/2026-04-30.csv", "listIds": [42], "updateExistingContacts": True,}Allt som är åtkomligt via en publik HTTPS-URL fungerar: pre-signerade S3-URL:er, GCS, din egen statiska host, även en GitHub raw-URL för enstaka importer. Brevo hämtar filen från din URL och kör sedan importen. Accepterade format: .csv, .txt, .json.
Skicka JSON i stället för CSV
Om du redan hämtar kontakter från en databas behöver du inte gå vägen via CSV. Skicka dem som JSON direkt:
body = { "jsonBody": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "attributes": { "FIRSTNAME": "John", "LASTNAME": "Smith", "COMPANY": "Globex", }, }, ], "listIds": [42],}Samma 10 MB-tak. Samma asynkrona beteende.
Polla efter färdigställning
Importen är asynkron. processId är ditt grepp för att följa den. Det finns en separat endpoint för att kontrollera processtatus:
def wait_for_import(process_id: int, timeout_s: int = 600) -> dict: import time deadline = time.time() + timeout_s while time.time() < deadline: r = requests.get( f"https://api.brevo.com/v3/processes/{process_id}", headers={"api-key": API_KEY}, timeout=30, ) r.raise_for_status() status = r.json() if status["status"] in ("completed", "failed"): return status time.sleep(5) raise TimeoutError(f"Import {process_id} did not finish in {timeout_s}s")
result = wait_for_import(process_id)print(f"Import {result['status']}: {result}")För långkörande jobb är det renare mönstret notifyUrl: skicka en HTTPS-endpoint som Brevo postar till när importen är klar, och hoppa över pollningen.
body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"Vanliga fel och hur du löser dem
400 Bad Request utan tydlig orsak. Nästan alltid semikolon kontra komma i CSV:n. Brevos import förväntar sig ;, inte ,. Dubbelkolla fileBody efter ditt konverteringssteg.
Värden för anpassade attribut försvinner. Attributet finns inte på ditt konto. Skapa det under Contacts → Settings → Contact attributes innan import, eller använd Attributes API för att skapa det som en del av ditt skript.
401 Unauthorized. Fel headernamn. Det är api-key (gemener, bindestreck), inte Authorization eller X-API-Key.
Importen “lyckades” men kontakterna dök inte upp i listan. Kontrollera att listIds innehåller rätt lista. Och: om updateExistingContacts är false och kontakterna redan finns hoppar Brevo tyst över dem i stället för att lägga till dem i listan.
Vissa rader importerades, andra inte. Brevo mejlar dig en felrapport per rad efter att importen är klar (om du inte satt disableNotification: true). Rapporten talar om vilka rader som hade ogiltiga e-postadresser, saknade obligatoriska fält eller formateringsfel.
När det är värt att skripta och när gränssnittet räcker
Gränssnittet duger för enstaka importer under tusen rader. Ett skript vinner så snart:
- Du importerar mer än en gång (t.ex. veckovis export från ditt CRM)
- Källdatan behöver städning innan den hamnar i Brevo (avduplicering, formatering av telefonnummer, uppdelning av fullständigt namn i för- och efternamn)
- Du vill att det ska köras enligt schema utan att någon klickar
- Filen är större än vad gränssnittet trivs med
Slå in skriptet ovan i ett cron-jobb eller en GitHub Action så har du automatiserad kontaktsynk. Nästa inlägg i serien visar hur du gör samma sak direkt från ett Google Sheet med Apps Script, ingen server krävs.