Importing contacts from CSV
Load contacts in bulk — with their custom fields — from a CSV file, via the dashboard or the API
When streaming contacts one by one through /v1/track isn't practical — migrating off another platform, seeding a fresh list, syncing a big batch — Bitelio lets you import them in bulk from a CSV file.
CSV format
Your file needs a header row, and email is the only column that must be present. Any additional column is stored on the contact as a custom field at data.<columnName>.
A minimal file:
email
ada@example.com
grace@example.com
linus@example.comA richer file with custom fields:
email,firstName,plan,signupDate
ada@example.com,Ada,pro,2026-01-15T00:00:00Z
grace@example.com,Grace,enterprise,2025-11-02T00:00:00Z
linus@example.com,Linus,free,2026-04-21T00:00:00ZWith this file, each imported contact gets data.firstName, data.plan, and data.signupDate.
Rules and limits
- File size: 5 MB maximum per upload. Bigger lists should be split across several imports.
- Encoding: UTF-8. Files in other encodings risk mangled custom field values.
- Email column: required and validated. Any row whose email is absent or malformed comes back in the error report.
- Reserved column names:
id,subscribed,createdAt,updatedAt, and the auto-generated URL variables (unsubscribeUrl, etc.) are dropped silently — leave them out of your file. - Date columns: format them as ISO 8601 (
2026-05-06T12:00:00Z) so Bitelio types them as dates, unlocking thewithin/olderThansegment operators. - Boolean columns: values of
true,false,yes,no(any casing) become booleans and show up as a boolean toggle in segment filters. - Numeric columns: bare integers and decimals (
42,3.14) become numbers, usable withgt/ltsegment operators. Values with leading zeros (01234), a+prefix, or scientific notation are kept as strings — this protects IDs, zip codes, and phone numbers from corruption. - Existing contacts: when a row's email already belongs to a contact, that contact is updated — the CSV's columns are merged into its
data. No duplicate is created and the rest of the record is left intact.
Importing your CSV
- Go to Contacts and hit Import.
- Select the CSV. Bitelio checks the header and previews the first few rows for you.
- Confirm — the import is queued and processed in the background.
- Once done, the Imports page shows the outcome, including any row-level errors.
For automation, use the import API:
Step 1 — upload the CSV
curl -X POST https://api.bitelio.com/contacts/import \
-H "Authorization: Bearer sk_..." \
-F "file=@contacts.csv"The response includes a jobId:
{ "jobId": "imp_abc123", "status": "queued" }Step 2 — poll for status
curl https://api.bitelio.com/contacts/import/imp_abc123 \
-H "Authorization: Bearer sk_..."Keep polling at a few-second interval until status reads completed or failed. The final response carries the counts:
{
"jobId": "imp_abc123",
"status": "completed",
"totalRows": 12000,
"imported": 11985,
"updated": 8,
"skipped": 0,
"errors": [
{ "row": 47, "email": "bad@", "reason": "invalid_email" },
{ "row": 1042, "email": "@example", "reason": "invalid_email" }
]
}imported is the number of contacts created from scratch, while updated counts existing ones whose data was patched. Each entry in errors gives you the row number and the reason, so problem rows can be corrected and re-uploaded.
Tips
- Unsubscribed contacts: every imported contact starts out subscribed. If the CSV contains people who never opted in, either set up a workflow that filters out anyone you shouldn't be emailing, or import first and then run
POST /contacts/bulk-unsubscribeso they stay on record without being mailable. - Custom fields appear in segments immediately: the moment the import completes, dynamic segments can filter on any of the imported columns.
- Idempotent reruns: uploading the same CSV again updates contacts in place instead of duplicating them — so after fixing a column, a re-upload pushes the corrected value to every matching row.
Related
- Custom fields — how the
data.<column>fields you import are typed and used. - Bulk operations —
bulk-subscribe,bulk-unsubscribe,bulk-deletefor mass actions on existing contacts (max 1,000 per call).