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.
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/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "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șijsonBodysunt 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 trimitefileUrl.- 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.
updateExistingContactsestetrueîn mod implicit. Dacă îl setezi pefalse, Brevo sare peste contactele al căror e-mail există deja, util pentru joburi de tipul “adaugă doar noi”.emptyContactsAttributescontrolează dacă celulele goale din CSV-ul tău șterg valorile existente. Implicitfalse(celulele goale sunt ignorate). Setează-l petruedacă 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ă.
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 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}")Rulează-l:
python import_csv_to_brevo.py contacts.csvPrimul 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,CITYAcesta 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:
- 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.
- Office Scripts + Power Automate, dacă registrul de lucru este în OneDrive/SharePoint și vrei sincronizare programată nesupravegheată. Acoperit și în ghidul Excel.
- 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 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")Dacă nu vrei pandas ca dependență, openpyxl singur funcționează:
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: aceeași treabă, SDK oficial
Brevo publică @getbrevo/brevo pentru Node. Se ocupă de autentificare, reîncercări și de forma tipată a cererii:
// 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}`);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:
# 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 . î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": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "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.