---
title: "Add a contact to a list (add or update)"
method: POST
path: "/lists/list/{listId}/contacts"
tags: ["List contacts"]
---

# Add a contact to a list (add or update)

`POST /lists/list/{listId}/contacts`

## Request

- Base URL: `https://api.emelia.io`
- URL: `https://api.emelia.io/lists/list/{listId}/contacts`
- Auth: API key in header `Authorization`

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: `job` lands in `jobStartDate` (a date, the request fails), `last` in `lastContacted` (the value is lost), `year` in `yearsOfExperience`, `date` in `birthDate`, `note` in the company `notes`, `tag` in the company `tags`.
- An unknown key whose value looks like a phone number (company registration number, order number, a date such as `2024-01-15`) is stored in `phone`, not in a new custom variable. Declare the custom variable first with `POST /lists/custom-variable`: an existing variable is matched before the value is inspected.
- `mobile` maps to `phone`. Use `mobilePhone` for 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 `emailVerified` and 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 example `2021-03-01`): an unparsable date makes the request fail.
- `null` and 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`.

## Path parameters

- `listId` string, required

## Query parameters

- `updateIfExists` 'true' | 'false'

## Request body

- ListsContactFields: 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.

## Response `200`

Contact created, duplicate ignored, or duplicate updated.

- ListsAddContactResult
  - `success` true, required
  - `leadId` string, required: Id of the created contact, or of the existing one when `duplicate` is true.
  - `companyId` string, nullable, required: Id of the company row, null when the contact has none.
  - `duplicate` boolean, required: True when a contact with the same email or LinkedIn URL already existed: nothing was created.
  - `updated` boolean, required: True when that existing contact was updated (`updateIfExists=true`).
  - `createdCustomVariables` ListsCreatedCustomVariable[], required
    - `technicalName` string, required
    - `displayName` string, required: The key as sent.
    - `schema` 'lead' | 'company', required: Always `lead` for variables created by the contact routes.

## Other responses

- `400`: The list id is not a valid ObjectId.
- `401`: Missing, unknown or disabled API key, or the account of the key no longer exists. Note the body shape: `error` and `code`, not `message`.
- `404`: The list does not exist or belongs to another account.
- `413`: JSON body larger than 5 MB.
- `422`: Invalid body.
- `500`: Unexpected error, for example an invalid date in `birthDate` or `jobStartDate`. The message is the raw internal message.

---

[API](https://skmtc.dev/emelia/apis/emelia-api.md) · [All operations](https://skmtc.dev/emelia/apis/emelia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/emelia/apis/emelia-api/revisions/8c0cb435f2c4?raw)
