Add a contact to a list (add or update)
Adds ONE contact to a list from a flat JSON object: field names as keys, scalar values (string, number or boolean). This is the endpoint for automations (Zapier, Make, n8n, scripts) and the standard way to feed a running campaign: if campaigns in RUNNING, PAUSED or DRAFT status use the list, the contact is propagated to them asynchronously (usually within seconds, at the latest by the nightly reconciliation). Adding contacts consumes no credits. For more than one contact, use the batch route.
How keys are resolved
Each key is resolved in this order: (1) case-insensitively against the recognized names of the standard fields (First Name, Prénom, email_address, Company...); (2) against the standard field keys, ignoring case, spaces, _ and - (firstName, first_name and FIRSTNAME all work), and also as a prefix or suffix of a field key when the key has 3 characters or more; (3) against your existing custom variables (technical or display name); (4) a key whose name ends in id is never matched by its value and becomes a custom variable; (5) otherwise the VALUE is inspected: an email-looking value goes to email, 6 digits or more go to phone, a linkedin.com/in/ URL goes to linkedinUrlProfile, any other http(s):// URL goes to the company websiteUrl. Only when nothing matched is a new lead custom variable created (display name = the key, technical name derived from it) and reported in createdCustomVariables.
Pitfalls
- Use the exact keys listed below. Short or generic keys are fuzzy-matched:
joblands injobStartDate(a date, the request fails),lastinlastContacted(the value is lost),yearinyearsOfExperience,dateinbirthDate,notein the companynotes,tagin the companytags. - An unknown key whose value looks like a phone number (company registration number, order number, a date such as
2024-01-15) is stored inphone, not in a new custom variable. Declare the custom variable first withPOST /lists/custom-variable: an existing variable is matched before the value is inspected. mobilemaps tophone. UsemobilePhonefor a mobile number.- A standard field takes at most one key per contact. When two keys target the same field, the second one moves to the next matching field (a second email key can end up in
emailVerifiedand be lost). - Numeric fields (
age,yearsOfExperience,employeeCount,foundedYear,annualRevenue) silently drop non-numeric values. Date fields (birthDate,jobStartDate) must be valid dates (use ISO 8601, for example2021-03-01): an unparsable date makes the request fail. nulland empty strings are ignored: this endpoint cannot clear a field. Arrays and objects are rejected (422).- Every unknown key creates an account-wide custom variable that cannot be deleted through the API.
Standard keys
Contact: firstName, lastName, fullName, email, secondaryEmail, phone, mobilePhone, linkedinUrlProfile, twitterUrl, jobTitle, seniority, department, gender, age, birthDate, language, country, region, city, postalCode, address, timezone, jobStartDate, yearsOfExperience, education, skills (list separated by commas, semicolons or pipes), bio, profilePictureUrl.
Company: companyName, companyDomain, companyEmail, websiteUrl, linkedinCompanyUrl, twitterCompanyUrl, logoUrl, industry, companySize, companyType, employeeCount, foundedYear, annualRevenue, notes, tags, techStack. The keys country, city, region, postalCode, address and timezone go to the contact: use Company Country, Company City or Company Address to fill the company ones.
Duplicates and idempotency
Before creating, the service looks in this list and the lists linked to it for a contact with the same email (exact comparison, case-sensitive), then with the same linkedinUrlProfile (exact string). If one is found, nothing is created: the response returns its leadId with duplicate: true. With updateIfExists=true the payload is applied to that contact instead (sent fields overwrite, absent fields are kept, identity fields included) and the response has updated: true. A contact without email and without LinkedIn URL is never deduplicated: retrying such a call creates a new contact each time. Send emails in lowercase and LinkedIn URLs in one canonical form.
The status code is 200 for a creation, an ignored duplicate and an update alike: read duplicate and updated.
Authorization
apiKey Your 48 character API key, sent as is (a Bearer prefix is tolerated). Create it at https://v3.emelia.io/settings/api.
In: header
Path Parameters
List id.
^[0-9a-fA-F]{24}$Query Parameters
Only the exact string true enables it. When a duplicate is found, apply the payload to the existing contact instead of ignoring it. Query parameter, never a body key (a body key would be stored as a contact field).
"false"Value in
- "true"
- "false"
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Flat contact object: one key per field, scalar values only. Standard keys are listed in the description of POST /lists/list/{listId}/contacts; any other key goes to an existing custom variable of the same name or creates a new one. Numbers and booleans are converted to strings. null and empty strings are ignored. Arrays and objects are rejected.
1 <= propertiesResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/lists/list/65f1a2b3c4d5e6f760f68df4/contacts" \ -H "Authorization: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "firstName": "John", "lastName": "Doe", "email": "john.doe@acme.com", "linkedinUrlProfile": "https://www.linkedin.com/in/john-doe-acme", "jobTitle": "Head of Sales", "companyName": "Acme", "websiteUrl": "https://www.acme.com", "icebreaker": "Loved your talk at SaaStock" }'{ "success": true, "leadId": "66f2a1b2c3d4e5f6a7b8c9d0", "companyId": "66f2a1b2c3d4e5f6a7b8c9e0", "duplicate": false, "updated": false, "createdCustomVariables": [ { "technicalName": "icebreaker", "displayName": "icebreaker", "schema": "lead" } ]}