Contacts
Manage contact profiles and custom fields, in bulk or one at a time.
Create a contact
Creates a contact and, optionally, subscribes it to audiences.
/contactsRequires a full API key. Email addresses are unique per workspace and stored in lowercase; creating a contact that already exists returns 409 with the existing contact. Fires contact.created, and subscriber.created for each audience. See Contacts.
Body parameters
emailstringrequiredfirst_namestringlast_namestringcustom_fieldsobjectValues by custom field key, such as {"company": "Analytical Engines"}. Values for date fields must be YYYY-MM-DD. Keys that don’t match a custom field are stored as they are.
audiencesstring[]aud_…) to subscribe the contact to. IDs that don’t exist in the workspace are skipped.unsubscribedbooleandefault: falsetrue to create the contact as unsubscribed. Unsubscribed contacts are skipped by campaigns, and their audience memberships start unsubscribed.
Returns
Returns 201 with the contact object. Here audiences lists each audience with its id, name and subscribed status. See Retrieve a contact for all fields.
Returns 422 with usage when an audience is at your plan’s subscriber limit. In that case no contact is created.
{
"object": "contact",
"id": "con_4K9kQdrXth7am0TPKvPrR5yd2oo",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"custom_fields": {
"company": "Analytical Engines",
"plan": "pro"
},
"unsubscribed": false,
"audiences": [
{
"id": "aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2",
"name": "Newsletter",
"subscribed": true
}
],
"created_at": "2026-10-01T10:20:31.704113Z",
"updated_at": "2026-10-01T10:20:31.704113Z"
}{
"error": "Custom field \"Birthday\" must be a date in YYYY-MM-DD format"
}{
"error": "Contact with this email already exists",
"existing": {
"object": "contact",
"id": "con_4K9kQdrXth7am0TPKvPrR5yd2oo",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"custom_fields": {
"company": "Analytical Engines",
"plan": "pro"
},
"unsubscribed": false,
"audiences": [
{
"id": "aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2",
"name": "Newsletter",
"subscribed": true
}
],
"created_at": "2026-10-01T10:20:31.704113Z",
"updated_at": "2026-10-01T10:20:31.704113Z"
}
}{
"error": "Pro includes 50,000 subscribers per audience.",
"usage": {
"used": 50000,
"limit": 50000,
"plan": "pro"
}
}Retrieve a contact
Retrieves a contact with its custom fields and audience memberships.
/contacts/{id}Requires a full API key.
Path parameters
idstringrequiredcon_…) or the contact’s email address, URL-encoded.Returns
Returns the contact object.
objectstringcontact.idstringemailstringfirst_namestring | nulllast_namestring | nullcustom_fieldsobject{} when there are none.unsubscribedbooleantrue if the contact unsubscribed from all campaigns.audiencesobject[]The audiences the contact belongs to, each with id, name and a subscriber object: id (sub_…), subscribed, subscribed_at, unsubscribed_at, created_at and updated_at.
created_atstringupdated_atstring{
"object": "contact",
"id": "con_4K9kQdrXth7am0TPKvPrR5yd2oo",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"custom_fields": {
"company": "Analytical Engines",
"plan": "pro"
},
"unsubscribed": false,
"audiences": [
{
"id": "aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2",
"name": "Newsletter",
"subscriber": {
"id": "sub_4KECYl5AwXEy8ezswYRhuddttxo",
"subscribed": true,
"subscribed_at": "2026-10-01T10:20:31.000000Z",
"unsubscribed_at": null,
"created_at": "2026-10-01T10:20:31.000000Z",
"updated_at": "2026-10-01T10:20:31.000000Z"
}
}
],
"created_at": "2026-10-01T10:20:31.000000Z",
"updated_at": "2026-10-01T10:20:31.000000Z"
}{
"error": "Contact not found"
}Update a contact
Updates a contact. Only the fields you send change.
/contacts/{id}Requires a full API key. Fires contact.updated, with the previous values of changed fields in previous. Changing audiences also fires subscriber.created and subscriber.deleted for the memberships it adds and removes.
Path parameters
idstringrequiredcon_…) or the contact’s email address, URL-encoded.Body parameters
emailstringfirst_namestringlast_namestringcustom_fieldsobjectunsubscribedbooleantrue to unsubscribe the contact from all campaigns, false to resubscribe. Existing audience memberships keep their own status.audiencesstring[]The complete list of audience IDs the contact should belong to. The contact is added to audiences that aren’t in its list yet and removed from audiences that aren’t in yours. Send [] to remove it from every audience. To add or remove one audience without listing all of them, use Add a subscriber or Delete a subscriber.
Returns
Returns the updated contact in the same format as Retrieve a contact. A request without any of these fields returns 400.
curl -X POST https://api.emailit.com/v2/contacts/ada%40example.com \
-H "Authorization: Bearer $EMAILIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"custom_fields": { "company": "Analytical Engines", "plan": "business" },
"audiences": ["aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2", "aud_4KOlh9t2uw5od4qypqCtyrZyDq2"]
}'{
"object": "contact",
"id": "con_4K9kQdrXth7am0TPKvPrR5yd2oo",
"email": "ada@example.com",
"first_name": "Augusta",
"last_name": "Lovelace",
"custom_fields": {
"company": "Analytical Engines",
"plan": "pro"
},
"unsubscribed": false,
"audiences": [
{
"id": "aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2",
"name": "Newsletter",
"subscriber": {
"id": "sub_4KECYl5AwXEy8ezswYRhuddttxo",
"subscribed": true,
"subscribed_at": "2026-10-01T10:20:31.000000Z",
"unsubscribed_at": null,
"created_at": "2026-10-01T10:20:31.000000Z",
"updated_at": "2026-10-01T10:20:31.000000Z"
}
}
],
"created_at": "2026-10-01T10:20:31.000000Z",
"updated_at": "2026-10-02T09:03:17.000000Z"
}{
"error": "No valid fields provided for update. Provide at least one of: email, first_name, last_name, custom_fields, unsubscribed, audiences"
}{
"error": "Contact not found"
}{
"error": "Another contact with this email already exists"
}List contacts
Returns a page of contacts, newest first.
/contactsRequires a full API key. Use the same parameters with Export contacts to download every match as a file.
Query parameters
pageintegerdefault: 1limitintegerdefault: 10searchstringq works too.audience_idstringaud_…).unsubscribedbooleantrue or false. Only contacts with this unsubscribed status.sortstringdefault: created_atemail, first_name, last_name, name, audiences, created_at or updated_at.orderstringdefault: descasc or desc. On this endpoint order is the sort direction, not the sort key.matchstringdefault: allall or or. How the filters below combine.Filters
Add filters as key.condition=value, for example email.ends_with=@acme.com or custom_fields.plan.exact=pro. See Filtering.
| Key | Type | Notes |
|---|---|---|
email |
string | |
first_name |
string | |
last_name |
string | |
name |
string | First and last name joined with a space. |
audiences |
string | The alphabetically first audience name of the contact. |
unsubscribed |
boolean | |
created_at |
date | |
updated_at |
date | |
audience_id |
string | Only exact and not_exact. The value is an audience ID. |
custom_fields.<key> |
string | Replace <key> with a custom field key. Values compare as text. |
The older filter[audience_id], filter[unsubscribed] and filter[custom_fields][<key>] parameters still work.
Returns
dataobject[]total_recordsintegernext_page_urlstring | nullnull. See Pagination.previous_page_urlstring | nullnull.curl -G https://api.emailit.com/v2/contacts \
-H "Authorization: Bearer $EMAILIT_API_KEY" \
--data-urlencode "audience_id=aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2" \
--data-urlencode "custom_fields.plan.exact=pro" \
--data-urlencode "sort=email" \
--data-urlencode "order=asc" \
--data-urlencode "limit=100"{
"data": [
{
"object": "contact",
"id": "con_4K9kQdrXth7am0TPKvPrR5yd2oo",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"custom_fields": {
"company": "Analytical Engines",
"plan": "pro"
},
"unsubscribed": false,
"audiences": [
{
"id": "aud_4KbFmQPy1feMCGAY8F8pAhiZ1u2",
"name": "Newsletter",
"subscribed": true
}
],
"created_at": "2026-10-01T10:20:31.704113Z",
"updated_at": "2026-10-01T10:20:31.704113Z"
},
{
"object": "contact",
"id": "con_4KiXIs4xLTcJIAHnKJaLcnMZYh7",
"email": "grace@example.com",
"first_name": "Grace",
"last_name": "Hopper",
"custom_fields": {},
"unsubscribed": true,
"audiences": [],
"created_at": "2026-09-28T07:55:02.118342Z",
"updated_at": "2026-09-30T18:11:40.902215Z"
}
],
"total_records": 2,
"next_page_url": null,
"previous_page_url": null
}Bulk update contacts
Runs one action on up to 100 contacts in a single request.
/contacts/bulkRequires a full API key. Every ID must belong to a contact in the workspace, otherwise nothing is changed and the response lists the missing IDs. Each contact fires the same events as the single-contact endpoints. If the audience reaches your plan’s subscriber limit during add_to_audience, the request stops with 422, and the contacts processed before that stay added.
| Action | What it does |
|---|---|
delete |
Deletes the contacts and their audience memberships, like Delete a contact. |
add_to_audience |
Adds the contacts to audience_id. Contacts already in it are left as they are. |
remove_from_audience |
Removes the contacts from audience_id. |
unsubscribe |
Sets unsubscribed to true, so campaigns skip the contacts. |
resubscribe |
Sets unsubscribed to false. |
Body parameters
actionstringrequireddelete, add_to_audience, remove_from_audience, unsubscribe or resubscribe.idsstring[]requiredcon_…), from 1 to 100. Email addresses aren’t accepted here. Duplicates are ignored.audience_idstringadd_to_audience and remove_from_audience.Returns
objectstringcontact_bulk.actionstringprocessedintegeridsstring[]{
"object": "contact_bulk",
"action": "add_to_audience",
"processed": 2,
"ids": ["con_4K9kQdrXth7am0TPKvPrR5yd2oo", "con_4KiXIs4xLTcJIAHnKJaLcnMZYh7"]
}{
"error": "A maximum of 100 contacts can be updated per request"
}{
"error": "One or more contacts were not found",
"missing": ["con_4K3pZc1Q9nWm2LrT8vYb5Hd0XaE"]
}{
"error": "Pro includes 50,000 subscribers per audience.",
"usage": {
"used": 50000,
"limit": 50000,
"plan": "pro"
}
}Export contacts
Downloads matching contacts as a CSV or XLSX file.
/contacts/exportRequires a full API key. Accepts the same search, filter and sort parameters as List contacts, passed in the query string, without pagination. POST /contacts/export works the same way. An export can include up to 10,000 contacts; if more match, the request returns 422, so narrow the filters.
Query parameters
formatstringdefault: csvcsv or xlsx.search, audience_id, unsubscribed, sort, order, match, key.conditionstringReturns
Returns the file as an attachment: contacts.csv (text/csv; charset=utf-8) or contacts.xlsx. Each row is a contact with these columns:
| Column | Contains |
|---|---|
email |
The email address. |
first_name, last_name |
The names. |
unsubscribed |
true or false. |
audiences |
The contact’s audience names, separated by ; . |
| One column per custom field | The value for each custom field defined in the workspace, named by its key. Lists are joined with ;. |
created_at, updated_at |
ISO 8601 timestamps. |
email,first_name,last_name,unsubscribed,audiences,company,plan,created_at,updated_at
ada@example.com,Ada,Lovelace,false,Newsletter,Analytical Engines,pro,2026-10-01T10:20:31.000000Z,2026-10-01T10:20:31.000000Z{
"error": "format must be csv or xlsx"
}{
"error": "Export is limited to 10000 contacts. Narrow your filters and try again."
}Delete a contact
Permanently deletes a contact and all of its audience memberships.
/contacts/{id}Requires a full API key. The delete can’t be undone. To stop emailing someone but keep their record, update the contact with unsubscribed: true, or add the address to your suppressions. Fires subscriber.deleted for each membership, then contact.deleted.
Path parameters
idstringrequiredcon_…) or the contact’s email address, URL-encoded.Returns
objectstringcontact.idstringemailstringdeletedbooleantrue.{
"object": "contact",
"id": "con_4Kt4ZXloQR8WGcMsYx8PFCUjokM",
"email": "alan@example.com",
"deleted": true
}{
"error": "Contact not found"
}