Project API

Contacts API

Create, update, list, and manage consent for your audience.

View as Markdown ↗

Project key. Acts in the project the key belongs to. An all-access key works too — name the project with an X-Project-Id header. How keys and scopes work.

Endpoints

GET/v1/contacts
POST/v1/contacts
GET/v1/contacts/export
GET/v1/contacts/{id}
PATCH/v1/contacts/{id}
DELETE/v1/contacts/{id}
POST/v1/contacts/{id}/subscribe
POST/v1/contacts/{id}/unsubscribe
POST/v1/contacts/verify
GET/v1/contacts/verify/{job_id}

Check a list before importing

POST /v1/contacts/verify checks 1–10,000 addresses without creating contacts or sending mail. Send { "emails": ["person@yourdomain.com"] }. Checks include syntax, domain mail routing, disposable/role-address signals, this project's known bounce or complaint history, and addresses known across Email Bump not to exist (known_to_bounce) or that our email provider won't deliver to (cant_be_delivered). This is not mailbox verification: ok means nothing known against the address, not proof that it exists or that you have consent.

Verdicts

undeliverableverdictoptional

A definitive blocking signal, such as invalid syntax, a reserved domain, no mail servers, null MX, or a known hard bounce or complaint.

riskyverdictoptional

An established warning, such as a role address, disposable domain or repeated soft bounces. This is not proof that the mailbox exists.

unknownverdictoptional

The lookup failed. Retry later; a failed lookup alone is not a reason to remove a contact.

okverdictoptional

No known blocking or risk signal. Not a mailbox-level verification or permission to send.

Each result includes score, reasons and provider. Read guidance for the check's limitations. Never treat verification as permission to send purchased or unsolicited lists.

Branch on mode

mode: "inline" includes results, summary and guidance. mode: "job" returns a queued job instead. Poll GET /v1/contacts/verify/{job_id}; its response is { "job": { ... } }. Stop when job.status is completed or failed, and check job.error on failure. A completed job supplies summary, guidance and a capped flagged list, not a full per-address results array. Up to three checks can be queued/running per project.

The dashboard CSV importer can apply verify_mode=basic (exclude undeliverable) or strict (also exclude risky). off skips those checks, but malformed and reserved-domain addresses are still rejected. A lookup failure alone never excludes a row; strict mode can still reject it for a separate established risk. Import is a dashboard session operation, not a public /v1/contacts/import endpoint.

Create or update a contact

This endpoint is an upsert. A new email creates a contact (201); an existing email updates the fields you send and leaves the rest untouched (200). The created flag in the response tells you which happened, so an import or sync loop is safe to re-run without handling conflicts. On an update, attributes are merged — keys you send win, keys you don't send stay, and an explicit null removes one. A field this endpoint doesn't take (say list_id instead of list_ids, or tags) is a 400 naming the valid fields, never silently dropped.

Body parameters

emailstringrequired

The contact's email address. Identifies the contact for the upsert.

first_namestringoptional

Given name.

last_namestringoptional

Family name.

companystringoptional

Company or organization.

citystringoptional

City.

countrystringoptional

Country.

phonestringoptional

Phone number.

timezonestringoptional

IANA timezone, e.g. America/New_York. Used by local-timezone and smart send times; we also learn it from where they open and click.

attributesobjectoptional

Any custom key/value attributes to store on the contact. Merged into anything already stored, so a second call never erases what the first recorded.

subscribedbooleanoptional

Marketing consent. Defaults to true; send false to store someone who did not opt in — they still receive transactional email, just not marketing. Omit it on an upsert and existing consent is left exactly as it was, so re-submitting a form can never revoke it; restating false keeps the original unsubscribed_at. true never resubscribes an address the project's Do not email list holds as unsubscribed or reported as spam — even one whose contact you have since deleted — and neither does leaving it out when the upsert creates the contact: the contact stays (or is created) unsubscribed, and the response carries a warnings entry saying why. Someone who unsubscribed can come back through POST /v1/contacts/{id}/subscribe once you have fresh consent; someone who reported spam only through a teammate, from the dashboard. A contact added with false, who never opted out, can still opt in with true. A change made here sends contact.subscribed or contact.unsubscribed.

list_idsstring[]optional

List ids to add the contact to in the same call. On a double-opt-in list the contact is added as pending and emailed a confirmation link; on a normal list they're added immediately.

cURL
curl -X POST https://emailbump.com/api/v1/contacts \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "company": "Acme",
    "attributes": { "plan": "pro" },
    "list_ids": ["5b3a0ea3-5c58-423e-92ff-0aaac7be0af3"]
  }'
201 created (200 when an existing contact is updated)
{
  "created": true,
  "contact": {
    "id": "5fef0867-b292-46ca-8371-e24d8575576c",
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "company": "Acme",
    "attributes": { "plan": "pro" },
    "subscribed": true,
    "email_status": "active",
    "created_at": "2026-07-21T19:56:38Z"
  }
}

Update a contact

PATCH /v1/contacts/{id} changes only the fields you send. It takes the same profile fields as create, minus email, plus two the upsert doesn't:

Body parameters

first_name, last_name, company, city, country, phone, timezone, attributessame as createoptional

Anything you leave out is untouched — including keys already inside attributes. The keys you send are added or overwritten; the rest are kept. Send a key as null to remove it. timezone takes null or an empty string to clear it.

subscribedbooleanoptional

Marketing consent, set on purpose, like the subscribe and unsubscribe endpoints. false unsubscribes a subscribed contact, stamping unsubscribed_at and putting the address on the Do not email list; for someone already unsubscribed it changes nothing. true lifts this project's unsubscribe, so only send it on fresh, explicit consent; for someone who marked your email as spam it is a 400, since only a teammate can clear that, from the dashboard. A change sends contact.subscribed or contact.unsubscribed.

email_statusstringoptional

Deliverability state. You will read active, hard_bounced, soft_bounced or complained, but the only value you can write is active, which clears a soft bounce and resets the soft-bounce count. Email Bump maintains the rest from delivery events, so any other value is accepted and then ignored — there is no way to suppress an address by hand. Writing active on a hard_bounced or complained contact is refused with a 400: those hold for every project in the workspace, so only a teammate can clear them, from the dashboard.

cURL
curl -X PATCH https://emailbump.com/api/v1/contacts/5fef0867-b292-46ca-8371-e24d8575576c \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "company": "Acme Corp", "attributes": { "plan": "enterprise" } }'

DELETE /v1/contacts/{id} removes the contact and its event history, and returns 204.

List & search

Filter by status (subscribed / unsubscribed for consent, or active / hard_bounced / soft_bounced / complained for deliverability; any other value is a 400) and search (email, name or company; not case-sensitive). To look one contact up by address, use email — an exact match that returns one contact or an empty set.

cURL
curl "https://emailbump.com/api/v1/contacts?search=jamie&status=subscribed&limit=25" \
  -H "Authorization: Bearer ebk_your_key"

curl "https://emailbump.com/api/v1/contacts?email=jamie@example.com" \
  -H "Authorization: Bearer ebk_your_key"

Export to CSV

GET /v1/contacts/export answers with the whole audience as a file — text/csv, not JSON. It supports status and search filters, has no pagination, and streams, so a large audience starts arriving before all of it has been read. Unlike the contacts list, status=subscribed exports only subscribed contacts whose email_status is active.

Query parameters

statusstringoptional

subscribed requires both marketing consent and email_status=active; unsubscribed requires consent to be false. Other non-empty values match email_status, for example hard_bounced. On the list endpoint, subscribed is consent alone and does not check deliverability.

searchstringoptional

Substring match on email, name or company.

list_iduuidoptional

Only contacts on this list, and the file gains a list_status column — confirmed, pending (double opt-in, never clicked) or unsubscribed (left this list, still in your audience).

segment_iduuidoptional

Only contacts matching this saved segment, evaluated when you ask rather than from anything cached.

They combine, and they combine with each other: a list scoped to a segment scoped to a search is one file.

cURL
curl "https://emailbump.com/api/v1/contacts/export?status=subscribed" \
  -H "Authorization: Bearer ebk_your_key" \
  -o contacts.csv

# One list, everyone on it, including who never confirmed
curl "https://emailbump.com/api/v1/contacts/export?list_id=5b3a0ea3-5c58-423e-92ff-0aaac7be0af3" \
  -H "Authorization: Bearer ebk_your_key" \
  -o newsletter.csv

The columns are email, first_name, last_name, company, phone, country, city, timezone and subscribed, then the state we maintain — email_status, unsubscribed_at, created_at, id — followed by list_status for a list-scoped export and custom-attribute columns. Export selects the first 200 distinct attribute keys alphabetically across the project, then excludes keys that collide with the profile or four state columns. It is not a complete attribute backup for projects above that limit. Objects and arrays are written as JSON text; strings, numbers and booleans are written plainly. Missing or null values are empty cells.

Re-importing is not a full restore

Email Bump recognizes the profile headers automatically; other providers may require column mapping. Automatic import skips email_status, unsubscribed_at, created_at, id and list_status: it does not restore delivery history or list-confirmation state. The subscribed column can change marketing consent, so retain only consent you can substantiate. Custom attributes undergo normal date, number and boolean inference; JSON objects and arrays exported as text are not reconstructed as structured values.

In the dashboard the same file is Export CSV: on Contacts it exports what the page is showing, search box and status filter included; on a list, that list; and on the Segments row menu, that segment.