Import through the API
For a few subscribers at a time (a sign-up, a checkout) use POST /subscribers. For a list, use the bulk import: it runs in the background, exactly like a file import in the app, and shows up on the History tab under Subscribers.
Start an import
Section titled “Start an import”POST /groups/{id}/import-subscribers imports 1 to 50,000 subscribers into one group. It needs Subscribers: manage.
| Key | Meaning |
|---|---|
subscribers |
The rows. Each row is an object with email (required) and fields (values by field key). No other keys. |
curl -X POST "$ECHO_API/groups/257e83a2-d55e-4a8c-b9a4-b3e939c151cc/import-subscribers" \ -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \ -d '{"subscribers":[ {"email":"[email protected]","fields":{"name":"Bence","city":"Győr"}}, {"email":"[email protected]","fields":{"name":"Réka","rendelesek_szama":5}}, {"email":"hibas-cim"} ]}'HTTP/1.1 202 AcceptedContent-Type: application/jsonX-Ratelimit-Limit: 5X-Ratelimit-Remaining: 4
{"data":{"import_id":"df745729-be25-495c-a5b1-8a070dbd18cb"}}The rate limit headers of this answer show the import bucket (5 a minute), see Errors and limits.
Every field key used in any row must exist in the workspace; otherwise nothing is imported:
{"error":{"code":"validation","message":"Please check the highlighted fields.","fields":{"fields.kedvenc_szin":"Unknown field."}}}An unknown group is 404 not_found; an empty list or more than 50,000 rows is 422 with fields.subscribers.
Follow the import
Section titled “Follow the import”GET /subscribers/import/{import_id} (Subscribers: view) shows the progress and the result. Poll it every few seconds until status is done, failed or canceled.
curl -H "Authorization: Bearer $ECHO_TOKEN" "$ECHO_API/subscribers/import/df745729-be25-495c-a5b1-8a070dbd18cb"{"data":{"id":"df745729-be25-495c-a5b1-8a070dbd18cb","kind":"csv","status":"done","filename":"api-import.csv","size_bytes":115,"encoding":"utf-8","delimiter":",","has_header":true,"preview":{"headers":[],"rows":[],"columns":0},"mapping":["email","city","name","rendelesek_szama"],"groups":[{"id":"257e83a2-d55e-4a8c-b9a4-b3e939c151cc","name":"Webshop vásárlók"}],"start_automations":false,"processed_rows":3,"added":2,"updated":0,"failed":1,"error":"","created_at":"2026-10-22T08:52:08.576581Z","started_at":"2026-10-22T08:52:08.759366Z","finished_at":"2026-10-22T08:52:08.810755Z","failed_rows":[{"row":3,"email":"hibas-cim","reason":"invalid_email","detail":""}]}}| Key | Meaning |
|---|---|
status |
queued (waiting for an earlier import), processing, done, failed or canceled. |
processed_rows |
Rows handled so far. |
added, updated, failed |
New subscribers, updated subscribers, rows left out. |
error |
parse_error or internal when the whole import failed, otherwise empty. |
mapping |
The columns: email, then the field keys used (in alphabetical order). |
start_automations |
Always false for API imports. |
failed_rows |
The first 100 rows left out: row (the row’s position in your subscribers array, from 1), email as sent (empty when missing), reason and detail (the field, when a value was refused). |
The API import is stored as a CSV named api-import.csv, so kind, filename, encoding, delimiter and has_header describe that file. failed counts every row left out, failed_rows lists the first 100. reason is one of missing_email, invalid_email, duplicate, role_address, short_gmail, suppressed, invalid_number, invalid_date, value_too_long or invalid_text. The import’s result page in the app (History tab, View) shows the same rows.
How rows are handled
Section titled “How rows are handled”- New addresses become
activesubscribers with the sourceimport, in the group. Imported subscribers get no confirmation email, so only import people who agreed to receive your emails. - Existing addresses are updated and added to the group; their status doesn’t change, so an unsubscribed subscriber stays unsubscribed.
- An empty value doesn’t erase a stored value.
- Rows fail for the same reasons as in the app: an invalid or missing address, a role address, an address on the suppression list, an address twice in the list, or a value that doesn’t fit the field type (numbers for number fields,
YYYY-MM-DDfor dates). - Imports don’t start automation workflows.
- Imports run one at a time per workspace; a second one waits as
queued. - When the import finishes, the workspace’s notification bell shows it; no email notification is sent for API imports.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Rows per request | 1 to 50,000 |
| Request body | 10 MB (413 too_large above it) |
| Imports per token | 5 a minute, on top of the 120 requests a minute |