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.

Featured image for article: Så importerar du CSV- eller Excel-kontakter till Brevo med ett skript (Python, Node.js, cURL)

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/import
Content-Type: application/json
api-key: YOUR_API_KEY
{
"fileBody": "EMAIL;FIRSTNAME;LASTNAME\n[email protected];Jane;Doe",
"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.
  • fileBody och jsonBody ä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 skickar fileUrl i 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.
  • updateExistingContacts har true som standard. Om du sätter det till false hoppar Brevo över kontakter vars e-post redan finns, användbart för jobb som “lägg bara till nya”.
  • emptyContactsAttributes styr om tomma celler i din CSV ska radera befintliga värden. Standard false (tomma celler ignoreras). Sätt till true om 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.

Terminal window
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_csv_to_brevo.py
import csv
import io
import os
import sys
import requests
API_KEY = os.environ["BREVO_API_KEY"]
LIST_ID = 42 # the Brevo list to add contacts to
INPUT_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:

Terminal window
python import_csv_to_brevo.py contacts.csv

Fö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,CITY
[email protected],Jane,Doe,Acme,Berlin
[email protected],John,Smith,Globex,Paris

Det ä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:

  1. 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.
  2. Office Scripts + Power Automate. Om arbetsboken ligger i OneDrive/SharePoint och du vill ha schemalagd synk utan tillsyn. Tas också upp i Excel-guiden.
  3. Konvertera .xlsx till 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 openpyxl
import 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_workbook
import 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:

import-csv-to-brevo.mjs
// npm install @getbrevo/brevo
import 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 needed
const 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:

Terminal window
# Convert commas to semicolons, then send the file inline
csv=$(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": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"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.

Vidare läsning

Frequently Asked Questions

Vilken är Brevos API-endpoint för massimport av kontakter?
POST https://api.brevo.com/v3/contacts/import. Den tar emot CSV-innehåll (fileBody), en URL till en extern fil (fileUrl) eller en JSON-array (jsonBody). Endpointen är asynkron: den returnerar ett processId direkt och slutför importen i bakgrunden.
Vad är den maximala filstorleken?
10 MB för CSV inline (fileBody) eller JSON (jsonBody). För större filer hostar du filen någonstans där den är åtkomlig och skickar dess URL via fileUrl i stället. Den vägen har ingen dokumenterad storleksgräns.
Hur importerar jag en .xlsx-fil?
Brevos import-API tar bara emot .csv, .txt eller .json. Konvertera först .xlsx till .csv. Pandas, openpyxl eller LibreOffice headless klarar det på en rad. Skriptet i den här guiden innehåller en konvertering från xlsx till csv.
Skrivs befintliga kontakter över?
Som standard, ja. updateExistingContacts har true som standardvärde och matchar på e-postadress. Sätt det till false om du vill hoppa över kontakter som redan finns. Sätt emptyContactsAttributes till true om du vill att tomma CSV-celler ska radera befintliga värden (annars ignoreras tomma celler).
Börja gratis med Brevo