Cum să imporți contacte CSV sau Excel în Brevo cu un script (Python, Node.js, cURL)

Importă în masă contacte dintr-un fișier CSV sau Excel în Brevo folosind API-ul import-contacts. Include scripturi Python și Node.js gata de rulat, un cURL one-liner, gestionarea erorilor și sfaturi pentru fișiere mai mari de 10 MB.

Featured image for article: Cum să imporți contacte CSV sau Excel în Brevo cu un script (Python, Node.js, cURL)

Dacă a trebuit vreodată să trimiți câteva mii de contacte dintr-o foaie de calcul în Brevo, calea manuală prin interfață devine rapid obositoare: alegi fișierul, mapezi coloanele, alegi o listă, aștepți, repeți. Un script face aceeași treabă în câteva secunde și (mai important) îl poți rula din nou pe un program, după un export de bază de date sau ori de câte ori CRM-ul tău scoate un CSV proaspăt.

Acest ghid acoperă endpoint-ul API pe care Brevo îl oferă exact pentru asta, cu scripturi complete funcționale în Python, Node.js și un cURL one-liner. Totul în acest ghid lovește POST /v3/contacts/import.

Endpoint-ul pe scurt

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
}

Răspunsul vine rapid:

{ "processId": 78 }

Acesta este un 202 Accepted. Brevo a acceptat importul și îl procesează în fundal. processId este mânerul tău de urmărire dacă vrei să verifici starea sau să configurezi un webhook notifyUrl.

Câteva lucruri de știut înainte să scrii cod:

  • Corpul poate fi CSV (fileBody), JSON (jsonBody) sau un URL la distanță (fileUrl). Alege unul. Forma CSV folosește punct și virgulă (;) pentru a separa coloanele, nu virgule, asta e o capcană obișnuită.
  • fileBody și jsonBody sunt limitate la 10 MB. Brevo recomandă să rămâi în jur de 8 MB pentru siguranță. Pentru orice mai mare, încarcă fișierul pe S3, GCS sau orice host HTTPS și trimite fileUrl.
  • Atributele personalizate care nu există în contul tău sunt ignorate în tăcere. Creează-le în interfața Brevo (sau prin Attributes API) înainte să imporți rânduri care le folosesc, altfel datele dispar pur și simplu.
  • updateExistingContacts este true în mod implicit. Dacă îl setezi pe false, Brevo sare peste contactele al căror e-mail există deja, util pentru joburi de tipul “adaugă doar noi”.
  • emptyContactsAttributes controlează dacă celulele goale din CSV-ul tău șterg valorile existente. Implicit false (celulele goale sunt ignorate). Setează-l pe true dacă CSV-ul tău este sursa de adevăr și vrei ca câmpurile goale să șteargă datele învechite.

Obține o cheie API

Conectează-te în Brevo → Setări → SMTP & API → API Keys → creează o cheie nouă. Arată ca xkeysib-.... O vei trimite ca header HTTP api-key la fiecare cerere. Tratează-o ca pe o parolă, are acces complet de citire/scriere la contul tău.

Practică curată: pune-o într-o variabilă de mediu astfel încât să nu ajungă niciodată în arborele tău sursă.

Terminal window
export BREVO_API_KEY="xkeysib-..."

Python: citește un CSV, trimite-l la Brevo

Acesta este cel mai simplu script posibil: citește un CSV local cu biblioteca standard, trimite corpul direct la Brevo. Nu este nevoie de SDK-uri terțe în afară de 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}")

Rulează-l:

Terminal window
python import_csv_to_brevo.py contacts.csv

Primul rând al CSV-ului tău este antetul. Numele coloanelor sunt mapate la atributele Brevo prin majuscule, potrivire exactă: EMAIL, FIRSTNAME, LASTNAME, plus orice atribut personalizat pe care l-ai definit. EMAIL este obligatoriu.

EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITY
[email protected],Jane,Doe,Acme,Berlin
[email protected],John,Smith,Globex,Paris

Acesta este întregul flux. Scriptul se întoarce în mai puțin de o secundă, importul propriu-zis rulează pe partea de server și durează între câteva secunde și câteva minute, în funcție de volum.

Fișiere Excel: trei căi viabile

Endpoint-ul de import al Brevo nu citește .xlsx direct, deci ai trei opțiuni reale, în funcție de unde trebuie să fie munca:

  1. Rulează un macro VBA în interiorul registrului de lucru, sincronizare cu un singur clic chiar din Excel, fără script extern. Răspunsul corect când fișierul stă pe un desktop și echipa ta vrea un buton pe foaie. Cod complet în ghidul macro VBA Excel-la-Brevo.
  2. Office Scripts + Power Automate, dacă registrul de lucru este în OneDrive/SharePoint și vrei sincronizare programată nesupravegheată. Acoperit și în ghidul Excel.
  3. Convertește .xlsx în CSV dintr-un script, ceea ce acoperă restul acestei secțiuni. Cel mai bine dacă rulezi deja Python sau Node pe un server și ai nevoie doar să tragi un registru de lucru o dată pe zi.

Pentru opțiunea 3, pandas o face într-o singură linie:

# 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")

Dacă nu vrei pandas ca dependență, openpyxl singur funcționează:

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: aceeași treabă, SDK oficial

Brevo publică @getbrevo/brevo pentru Node. Se ocupă de autentificare, reîncercări și de forma tipată a cererii:

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}`);

Sau, dacă nu vrei SDK-ul, simplu fetch funcționează la fel:

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: un one-liner pentru importuri ad-hoc

Când vrei doar să testezi endpoint-ul sau să împingi un fișier mic manual:

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 . înghite întregul fișier și îl JSON-escape, te scapă de escape-ul manual al noilor linii și al ghilimelelor.

Fișiere mai mari de 10 MB: folosește fileUrl

fileBody este limitat la 10 MB. Dacă dump-ul tău de contacte este mai mare, găzduiește fișierul la un URL pe care Brevo îl poate prelua și trimite-l:

body = {
"fileUrl": "https://files.example.com/contacts/2026-04-30.csv",
"listIds": [42],
"updateExistingContacts": True,
}

Orice e accesibil printr-un URL HTTPS public funcționează, URL-uri S3 pre-semnate, GCS, propriul tău host static, chiar și un URL raw GitHub pentru importuri unice. Brevo preia fișierul de la URL-ul tău, apoi rulează importul. Formate acceptate: .csv, .txt, .json.

Trimite JSON în loc de CSV

Dacă deja extragi contacte dintr-o bază de date, nu trebuie să faci ocol prin CSV, trimite-le direct ca JSON:

body = {
"jsonBody": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "John",
"LASTNAME": "Smith",
"COMPANY": "Globex",
},
},
],
"listIds": [42],
}

Aceeași limită de 10 MB. Același comportament asincron.

Verificarea stării

Importul este asincron, processId este mânerul tău pentru a-l urmări. Există un endpoint separat pentru a verifica starea procesului:

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}")

Pentru joburi de lungă durată, modelul mai curat este notifyUrl: trimite un endpoint HTTPS pe care Brevo va trimite POST când importul se termină și sari peste verificări.

body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"

Erori comune și cum să le repari

400 Bad Request fără cauză evidentă. Aproape întotdeauna punct și virgulă vs. virgule în CSV. Importul Brevo așteaptă ;, nu ,. Verifică din nou fileBody după pasul de conversie.

Valorile atributelor personalizate dispar. Atributul nu există în contul tău. Creează-l sub Contacte → Setări → Atribute de contact înainte de import sau folosește Attributes API pentru a-l crea ca parte a scriptului tău.

401 Unauthorized. Nume greșit de header. Este api-key (litere mici, cu cratimă), nu Authorization sau X-API-Key.

Importul “a reușit”, dar contactele nu au apărut în listă. Verifică dacă listIds conține lista corectă. De asemenea: dacă updateExistingContacts este false și contactele există deja, Brevo le sare în tăcere în loc să le re-adauge la listă.

Unele rânduri importate, altele nu. Brevo îți trimite un raport de eroare per rând prin e-mail după ce importul se termină (cu excepția cazului în care setezi disableNotification: true). Raportul îți spune ce rânduri aveau e-mailuri proaste, câmpuri obligatorii lipsă sau probleme de formatare.

Când să folosești script vs. interfață

Interfața este în regulă pentru importuri unice sub o mie de rânduri. Un script câștigă imediat ce:

  • Imporți de mai multe ori (de exemplu, export săptămânal din CRM-ul tău)
  • Datele sursă au nevoie de curățare înainte să ajungă în Brevo (deduplicare, formatare numere de telefon, împărțirea numelor complete în prenume/nume)
  • Vrei să ruleze pe un program fără ca cineva să apese butoane
  • Fișierul este mai mare decât zona de confort a interfeței

Învelește scriptul de mai sus într-un cron job sau un GitHub Action și ai sincronizare automată de contacte. Următorul articol din această serie arată cum să faci același lucru direct dintr-un Google Sheet folosind Apps Script, fără server.

Lectură suplimentară

Frequently Asked Questions

Care este endpoint-ul Brevo API pentru importul în masă de contacte?
POST https://api.brevo.com/v3/contacts/import. Acceptă conținut CSV (fileBody), URL-ul unui fișier la distanță (fileUrl) sau un array JSON (jsonBody). Endpoint-ul este asincron, returnează imediat un processId și finalizează importul în fundal.
Care este dimensiunea maximă a fișierului?
10 MB pentru CSV inline (fileBody) sau JSON (jsonBody). Pentru fișiere mai mari, găzduiește fișierul undeva accesibil și trimite URL-ul prin fileUrl, această cale nu are o limită de dimensiune documentată.
Cum import un fișier .xlsx?
API-ul de import Brevo acceptă doar .csv, .txt sau .json. Convertește mai întâi .xlsx în .csv, pandas, openpyxl sau LibreOffice headless o pot face într-o singură linie. Scriptul din acest ghid include o conversie xlsx în csv.
Va suprascrie contactele existente?
În mod implicit, da, updateExistingContacts este setat pe true și potrivește pe baza e-mailului. Setează-l pe false pentru a sări peste contactele care există deja. Setează emptyContactsAttributes pe true dacă vrei ca celulele goale din CSV să șteargă valorile existente (altfel celulele goale sunt ignorate).
Începe gratuit cu Brevo