lemlist Contacts API
CRM-side contacts, contact lists, list membership and exports.
CRM-side contacts, contact lists, list membership and exports.
openapi: 3.0.0
info:
title: lemlist Contacts API
version: 1.0.0
description: The Contacts operations of the lemlist API, split by tag from the OpenAPI lemlist publishes
at https://developer.lemlist.com/api-reference/openapi/v2.json. Operation content is carried verbatim
from the provider spec.
servers:
- url: https://api.lemlist.com/api
security:
- basicAuth: []
tags:
- name: Contacts
paths:
/contacts:
get:
summary: Get Many Contacts
description: 'Retrieves contacts by IDs/emails, or searches/lists contacts by name, email, contact
list, campaign membership, or company link.
When using `idsOrEmails`, returns an array of matching contacts directly.
When using `search`, `email`, `listId`, `notInAnyCampaign`, any of the `company*` filters, or
no filter at all, returns a paginated response with `data`, `total`, `limit`, and `offset` fields.
You can combine filters together to narrow results (e.g. `listId` with `search`, or `notInAnyCampaign`
with `companyId`). Calling the endpoint without any filter returns all contacts of the team, paginated.
The `company*` filters (`companyId`, `companyDomain`, `companyLinkedinUrl`, `companySalesnavUrl`)
are mutually exclusive — use only one at a time. `companyDomain` / `companyLinkedinUrl` / `companySalesnavUrl`
are resolved to a `companyId` through the Companies collection; if no matching company exists,
the endpoint returns an empty list with `total: 0` (not an error), which keeps automation flows
simple.'
tags:
- Contacts
parameters:
- name: idsOrEmails
in: query
required: false
description: A comma separated string of either valid contact IDs (MongoDB ObjectId) or valid
email addresses. Optional — when omitted, returns the paginated list of all contacts of the
team. Maximum 100 values.
example: ctc_xW8Ou6C03Csv8vatp,riley@example.com
schema:
type: string
style: form
explode: false
- name: search
in: query
required: false
description: Search contacts by name or other text fields. Must be at least 2 characters.
schema:
type: string
- name: email
in: query
required: false
description: Search contacts by exact email address.
schema:
type: string
format: email
- name: listId
in: query
required: false
description: Filter contacts by contact list ID (`clt_xxx` format). Can be combined with `search`
or `email`, or used alone to list all contacts in a list. Get valid IDs from `GET /contacts/lists`.
example: clt_abc123def456ghi78
schema:
type: string
pattern: ^clt_[a-zA-Z0-9]+$
- name: notInAnyCampaign
in: query
required: false
description: When set to `true`, only returns contacts that are not part of any campaign (orphan
contacts). Can be used alone or combined with other filters such as `search`, `email`, or `listId`.
schema:
type: boolean
- name: companyId
in: query
required: false
description: Filter contacts by attached company ID (`cpn_xxx` format). Use this when you already
know the lemlist company id (for example after fetching `GET /companies?crmSyncStatus=unique_index_error_company`).
Mutually exclusive with `companyDomain`, `companyLinkedinUrl`, and `companySalesnavUrl`.
example: cpn_A1B2C3D4E5F6G7H8I
schema:
type: string
pattern: ^cpn_[a-zA-Z0-9]+$
- name: companyDomain
in: query
required: false
description: 'Filter contacts by their company''s website domain. Resolved to a `companyId` against
the Companies collection. If no company matches, the endpoint returns an empty list (`total:
0`). Mutually exclusive with the other `company*` filters.'
example: acme.com
schema:
type: string
- name: companyLinkedinUrl
in: query
required: false
description: 'Filter contacts by their company''s LinkedIn URL. Resolved to a `companyId` against
the Companies collection. If no company matches, the endpoint returns an empty list (`total:
0`). Mutually exclusive with the other `company*` filters.'
example: https://www.linkedin.com/company/acme
schema:
type: string
- name: companySalesnavUrl
in: query
required: false
description: 'Filter contacts by their company''s LinkedIn Sales Navigator URL. Resolved to a
`companyId` against the Companies collection. If no company matches, the endpoint returns an
empty list (`total: 0`). Mutually exclusive with the other `company*` filters.'
example: https://www.linkedin.com/sales/company/12345678
schema:
type: string
- name: fieldRejectionReason
in: query
required: false
description: 'Filter contacts to those carrying a field rejection with this reason — a value lemlist
refused to write, prefixed by its origin (`enrichment_*` while enriching, `crm_sync_*` during
CRM sync). Returns an empty list (`total: 0`) when no contact matches. Each returned contact
exposes the full detail under `fieldRejections[]` (which field, why, and `conflictingRecordId`
for duplicates). Only applies to the paginated list — ignored when `idsOrEmails` is provided
(that path returns the exact contacts requested, unfiltered).'
example: enrichment_duplicate_linkedin_url
schema:
type: string
enum:
- enrichment_duplicate_linkedin_url
- enrichment_duplicate_email
- crm_sync_duplicate_linkedin_url
- crm_sync_invalid_linkedin_url
- crm_sync_invalid_phone
- crm_sync_duplicate_contact_blocked
- crm_sync_duplicate_company_blocked
- crm_sync_company_not_in_lemlist
- crm_sync_custom_field_creation_failed
- crm_sync_value_oscillating
- crm_sync_owner_sync_loop
- crm_sync_unmapped_user
- crm_sync_value_incompatible
- crm_sync_unknown_error
- name: limit
in: query
required: false
description: Maximum number of contacts to return (1–500). Defaults to 100.
schema:
type: integer
minimum: 1
maximum: 500
default: 100
- name: offset
in: query
required: false
description: Number of contacts to skip for pagination. Defaults to 0.
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: Success. When using `idsOrEmails`, returns an array of contacts. Otherwise, returns
a paginated object with `data`, `total`, `limit`, and `offset`.
content:
application/json:
schema:
oneOf:
- type: array
items:
$ref: '#/components/schemas/Contact'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Contact'
total:
type: integer
limit:
type: integer
offset:
type: integer
required:
- data
- total
- limit
- offset
example:
- _id: ctc_xW8Ou6C03Csv8vatp
teamId: tea_8QvkOiBfPdb2ZRhHi
fullName: John Doe
email: support@lemlist.com
fields:
firstName: John
jobTitle: Growth Engineer
lastName: Doe
industry: Technology
isActiveInCampaigns: false
lastCampaign: NEW TO DELETE
lastLeadMarkedAsInterestedDate: '2025-10-28T02:12:31.971Z'
leadStatus: Marked as not Interested by api
campaigns:
- campaignId: cam_bSn8EORHQxbWPjHvu
campaignState: running
leadState: review
leadId: lea_fiDpiGV585wy3Oii2
ownerId: usr_ahfFktBBHUIxbVG5P
createdAt: '2025-10-28T00:40:37.917Z'
createdBy: usr_ahfFktBBHUIxbVG5P
unsubscribed: false
- _id: ctc_a9RxJNa7pmMd85H9b
teamId: tea_8QvkOiBfPdb2ZRhHi
fullName: Casey
email: riley@example.com
fields:
firstName: Casey
isActiveInCampaigns: false
campaigns:
- campaignId: cam_jwm7THjgGFE3ylR85
campaignState: running
leadState: done
leadId: lea_XKjAytuJhBKZhxhWh
- campaignId: cam_eF4DlNERV0CW1TwRd
campaignState: running
leadState: done
leadId: lea_fJcS9D3UtEqZcDcAG
- campaignId: cam_UBbMt30jHq0vNJKJr
campaignState: running
leadState: done
leadId: lea_GlaMfjxlUYuwEDL0w
- campaignId: cam_pijDVnytN5S7frriD
campaignState: running
leadState: review
leadId: lea_Bs9aMGCcjdzTDvixY
ownerId: usr_Emu1g29BMtBixhMSP
createdAt: '2024-10-01T09:00:13.831Z'
createdBy: usr_Emu1g29BMtBixhMSP
unsubscribed: true
'400':
description: 'Possible errors: Bad team / search query too short / invalid listId format'
content:
text/plain:
example: Search query must be at least 2 characters
'401':
description: The authentication you supplied is incorrect
content:
text/plain:
example: The authentication you supplied is incorrect
'405':
description: Method not allowed
post:
summary: Add and update contact
description: Creates a new contact or updates an existing one (upsert). If a contact with the same
email, LinkedIn URL, or Sales Navigator URL already exists, it will be updated with the provided
non-empty fields. Null or empty values are ignored during updates to preserve existing data. You
can target an existing contact directly by providing `contactId`, bypassing email/LinkedIn matching.
You can optionally link the contact to a company by providing `companyId`, `companyDomain`, or
`companyLinkedinUrl`.
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contactId:
type: string
description: Existing contact ID. Updates a specific contact by ID, bypassing email/LinkedIn
matching. Can only be used to update an existing contact, not to create a new one.
When provided, `email` and `linkedinUrl` are not required. At least one of `contactId`,
`email`, `linkedinUrl`, or `linkedinUrlSalesNav` is required.
email:
type: string
description: Contact email address. Used as a unique key for upsert matching. At least
one of `contactId`, `email`, `linkedinUrl`, or `linkedinUrlSalesNav` is required.
linkedinUrl:
type: string
description: LinkedIn profile URL. Used as an alternative unique key for upsert matching.
At least one of `contactId`, `email`, `linkedinUrl`, or `linkedinUrlSalesNav` is required.
linkedinUrlSalesNav:
type: string
description: LinkedIn Sales Navigator profile URL. Used as an alternative unique key
for upsert matching.
additionalEmails:
type: array
items:
type: string
description: Additional email addresses for the contact. Each must be a valid email
address.
firstName:
type: string
description: Contact first name.
lastName:
type: string
description: Contact last name.
phone:
type: string
description: Contact phone number.
jobTitle:
type: string
description: Contact job title. If a company is linked, this is saved as part of the
job data.
jobDescription:
type: string
description: Contact job description. If a company is linked, this is saved as part
of the job data.
picture:
type: string
description: URL of the contact's profile picture.
timezone:
type: string
description: Contact timezone.
industry:
type: string
description: Contact industry.
languages:
type: string
description: Contact languages.
location:
type: string
description: Contact location.
skills:
type: string
description: Contact skills.
summary:
type: string
description: Contact summary or bio.
tagline:
type: string
description: Contact tagline.
contactOwner:
type: string
description: Owner of the contact. Can be a user ID (e.g. `usr_...`) or a team member's
email address. If the provided value does not match a team member, the owner defaults
to the API key owner.
source:
type: string
description: Origin of the contact record. Set on creation only and cannot be updated
afterwards. Defaults to `api`.
default: api
companyId:
type: string
description: ID of a company already existing in lemlist to link to this contact. Takes
priority over `companyDomain` and `companyLinkedinUrl`.
companyDomain:
type: string
description: Domain of a company already existing in lemlist to link to this contact
(e.g. `lemlist.com`). Used if `companyId` is not provided.
companyLinkedinUrl:
type: string
description: LinkedIn URL of a company already existing in lemlist to link to this contact.
Used if `companyId` and `companyDomain` are not provided.
additionalProperties:
description: Any additional key is treated as a custom field. Custom fields must be registered
in the team's CRM field registry beforehand.
example:
email: jane.smith@example.com
firstName: Jane
lastName: Smith
linkedinUrl: https://linkedin.com/in/janesmith
jobTitle: Product Manager
companyDomain: acme.com
responses:
'201':
description: Contact created successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
_id:
type: string
description: Unique contact identifier
teamId:
type: string
description: Team ID
emails:
type: array
description: Contact email addresses
items:
type: object
properties:
value:
type: string
linkedinUrl:
type: string
description: LinkedIn profile URL
linkedinUrlSalesNav:
type: string
description: LinkedIn Sales Navigator URL
fields:
type: object
description: Key/value fields (firstName, lastName, phone, jobTitle, custom fields,
etc.)
additionalProperties: true
ownerId:
type: string
description: Owner user ID
companyId:
type: string
description: Linked company ID (present only when a company was linked)
source:
type: string
description: Record origin
createdAt:
type: string
format: date-time
description: Creation timestamp
createdBy:
type: string
description: User ID who created the record
unsubscribed:
type: boolean
description: Whether the contact is unsubscribed
created:
type: boolean
description: '`true` when a new contact was created, `false` when an existing
contact was updated.'
updated:
type: boolean
description: Present and set to `true` only when an existing contact was updated
(i.e. `created` is `false`).
companyLinked:
type: boolean
description: Whether a company was successfully linked to the contact.
warnings:
type: array
description: Optional warnings (e.g. company resolution issues).
items:
type: object
properties:
code:
type: string
message:
type: string
example:
success: true
data:
_id: ctc_xW8Ou6C03Csv8vatp
teamId: tea_8QvkOiBfPdb2ZRhHi
emails:
- value: jane.smith@example.com
linkedinUrl: https://www.linkedin.com/in/janesmith
fields:
firstName: Jane
lastName: Smith
jobTitle: Product Manager
ownerId: usr_ahfFktBBHUIxbVG5P
source: api
createdAt: '2025-10-28T00:40:37.917Z'
createdBy: usr_ahfFktBBHUIxbVG5P
unsubscribed: false
created: true
companyLinked: true
companyId: cpn_gG7PsmZFpEAnpMCHO
'200':
description: Existing contact updated (upsert matched by email or LinkedIn URL)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
description: 'Same shape as the 201 response, with `created: false` and `updated:
true`.'
example:
success: true
data:
_id: ctc_xW8Ou6C03Csv8vatp
teamId: tea_8QvkOiBfPdb2ZRhHi
emails:
- value: jane.smith@example.com
fields:
firstName: Jane
lastName: Smith
jobTitle: Product Manager
ownerId: usr_ahfFktBBHUIxbVG5P
source: api
createdAt: '2025-10-28T00:40:37.917Z'
createdBy: usr_ahfFktBBHUIxbVG5P
unsubscribed: false
created: false
updated: true
companyLinked: false
'400':
description: Validation error
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
error:
type: object
properties:
code:
type: string
description: 'Error code: `MISSING_IDENTIFIER`, `INVALID_CONTACT_ID`, `CONTACT_NOT_FOUND_BY_ID`,
`INVALID_EMAIL`, `INVALID_ADDITIONAL_EMAILS`, `INVALID_LINKEDIN_URL`, `INVALID_FIELD`,
or `CONTACT_VALIDATION_FAILED`.'
message:
type: string
example:
success: false
error:
code: MISSING_IDENTIFIER
message: 'At least one identifier is required: email, linkedinUrl or contactId'
'401':
description: The authentication you supplied is incorrect
content:
text/plain:
example: The authentication you supplied is incorrect
parameters: []
/contacts/{idOrEmail}:
delete:
summary: Delete Contact
description: Deletes a lemlist contact resolved by its id (`ctc_xxx`) or email. Cascades to the
contact's leads, opportunities, list associations, inbox conversations and activities. **No CRM-side
propagation** — only the lemlist record is removed. Fails with `409 CONTACT_DELETE_BLOCKED` when
the contact cannot be deleted right now — for example while an enrichment is still running (retry
once it finishes); the response `error.message` states the specific reason.
tags:
- Contacts
responses:
'200':
description: Contact deleted.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
deletedContactId:
type: string
description: ID of the deleted lemlist contact.
required:
- success
- deletedContactId
example:
success: true
deletedContactId: ctc_xW8Ou6C03Csv8vatp
'401':
description: The authentication you supplied is incorrect.
content:
text/plain:
example: The authentication you supplied is incorrect
'404':
description: Contact not found (`CONTACT_NOT_FOUND`).
content:
application/json:
example:
success: false
error:
code: CONTACT_NOT_FOUND
message: No contact found with the provided id or email
'409':
description: The contact cannot be deleted right now (`CONTACT_DELETE_BLOCKED`) — for example
while an enrichment is in progress. The `error.message` states the specific reason; retry
once it clears.
content:
application/json:
example:
success: false
error:
code: CONTACT_DELETE_BLOCKED
message: Contact cannot be removed if an enrichment is in progress
'405':
description: Method not allowed.
parameters:
- name: idOrEmail
in: path
required: true
description: The unique identifier or email of the contact
example: ctc_XXBXpAxmxwg24IzBI
schema:
type: string
get:
summary: Get Contact
tags:
- Contacts
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Contact'
example:
_id: ctc_xW8Ou6C03Csv8vatp
teamId: tea_8QvkOiBfPdb2ZRhHi
fullName: John Doe
email: support@lemlist.com
fields:
firstName: John
jobTitle: Growth Engineer
lastName: Doe
industry: Technology
isActiveInCampaigns: false
lastCampaign: NEW TO DELETE
lastLeadMarkedAsInterestedDate: '2025-10-28T02:12:31.971Z'
leadStatus: Marked as not Interested by api
campaigns:
- campaignId: cam_bSn8EORHQxbWPjHvu
campaignState: running
leadState: review
leadId: lea_fiDpiGV585wy3Oii2
ownerId: usr_ahfFktBBHUIxbVG5P
createdAt: '2025-10-28T00:40:37.917Z'
createdBy: usr_ahfFktBBHUIxbVG5P
unsubscribed: false
'400':
description: Bad team
content:
text/plain:
example: Bad team
'401':
description: The authentication you supplied is incorrect
content:
text/plain:
example: The authentication you supplied is incorrect
'405':
description: Method not allowed
parameters: []
/contacts/lists:
get:
summary: Get Contact Lists
description: Retrieves all static and dynamic contact lists for the team. Optionally filter by name.
tags:
- Contacts
parameters:
- name: search
in: query
required: false
description: Filter lists by name (case-insensitive partial match).
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
type: object
properties:
_id:
type: string
description: Unique list ID (`clt_xxx` format)
name:
type: string
description: Name of the contact list
dynamic:
type: boolean
description: Whether the list is dynamic (auto-populated by filters) or static (manually
managed)
example:
- _id: clt_abc123def456ghi78
name: Nurturing
dynamic: false
- _id: clt_xyz789uvw012rst34
name: Enterprise Prospects
dynamic: true
'400':
description: Bad team
content:
text/plain:
example: Bad team
'401':
description: The authentication you supplied is incorrect
content:
text/plain:
example: The authentication you supplied is incorrect
'405':
description: Method not allowed
post:
summary: Create Contact List
description: Creates a new static contact list. After creating the list, use `POST /contacts/lists/{listId}/entities`
to add contacts to it.
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
maxLength: 200
description: Name of the contact list to create (max 200 characters).
example:
name: Nurturing
responses:
'201':
description: Contact list created successfully
content:
application/json:
schema:
type: object
properties:
message:
type: string
list:
type: object
properties:
id:
type: string
description: Unique list ID (`clt_xxx` format)
name:
type: string
description: Name of the created list
example:
message: Contact list created successfully
list:
id: clt_abc123def456ghi78
name: Nurturing
'400':
description: 'Possible errors: Bad team / name is required / name too long'
content:
text/plain:
example: name is required and must be a non-empty string
'401':
description: The authentication you supplied is incorrect
content:
text/plain:
example: The authentication you supplied is incorrect
'405':
description: Method not allowed
/contacts/lists/{listId}/entities:
post:
summary: Add or Remove Contacts in a List
description: 'Adds existing CRM contacts to a static contact list, or removes them when `?action=remove`
is set.
- **Add** (default): send a `POST` with a `contactIds` body. Contacts already in the list are
silently skipped (no duplicates) and reported in `alreadyInList`.
- **Remove**: send a `POST` with `?action=remove` and a `contactIds` body. Contacts that are not
in the list are silently skipped; `removedCount` reflects the associations actually removed. This
only affects list membership, the contacts are not deleted from your CRM.
The list must be a **static** contact list (`clt_xxx`). Dynamic lists (auto-populated by filter
rules) and company lists are rejected.
**Removal uses `POST ?action=remove` on purpose:** a `DELETE` request body is dropped by our stack,
so `DELETE` on this path is **not supported** and returns `405 Method Not Allowed`.'
tags:
- Contacts
parameters:
- name: action
in: query
required: false
description: Omit to **add** the contacts to the list. Set to `remove` to **remove** them from
the list.
example: remove
schema:
type: string
enum:
- remove
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- contactIds
properties:
contactIds:
type: array
items:
type: string
pattern: ^ctc_[a-zA-Z0-9]+$
minItems: 1
maxItems: 1000
description: Array of contact IDs (`ctc_xxx` format) to add to or remove from the list.
Required, non-empty, maximum 1,000 per request.
example:
contactIds:
- ctc_xxxxxxxxxxxxxxxxx
- ctc_yyyyyyyyyyyyyyyyy
responses:
'200':
description: Contacts added to or removed from the list successfully. The response body depends
on `action`.
content:
application/json:
schema:
type: object
proper
# --- truncated at 32 KB (39 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/lemlist/refs/heads/main/openapi/lemlist-contacts-api-openapi.yml