Learning Objective
By the end of this tutorial, you'll know how to identify lemlist companies blocked from CRM sync by a duplicate domain conflict, remap their contacts to the correct record, and automate the full cleanup workflow using the lemlist API — without any manual work in the lemlist UI.
Why This Matters
When contacts can't sync to your CRM, your sales team works with incomplete data. Deals go unfollowed, accounts are invisible in HubSpot, Salesforce, or Pipedrive, and pipeline gaps are silent — no error surfaces to the rep. UNIQUE_INDEX_ERROR_COMPANY is one of the most common causes of this, and for teams with large contact databases it can silently block hundreds of records at once.
The error occurs because lemlist uses company domain as a unique identifier when syncing. If two lemlist companies share the same domain, only the first one to sync succeeds. Every contact linked to the second is blocked until you resolve the conflict.
Without API access, the only fix is manual — opening each duplicate in the lemlist UI and correcting it one at a time. The three endpoints in this tutorial let you automate the entire process and run it on a schedule, so new conflicts are resolved before your team ever notices them.
Prerequisites
You have a lemlist API key — see the lemlist API documentation
Your CRM (HubSpot, Salesforce, or Pipedrive) is connected to lemlist
You're comfortable making HTTP API calls
Optional: access to n8n, Zapier, or Make to run the workflow on a schedule
Core Lesson — Step-by-Step Workflow
Phase 1: Get to know the three API endpoints
Three endpoints do the work. Before building your automation, take a few minutes to understand what each one returns — it'll make Phase 2 much clearer.
1. GET /api/companies — filter by CRM sync status
The new crmSyncStatus query parameter filters companies by their sync state. Pass unique_index_error_company to return only companies blocked by a domain conflict.
GET /api/companies?crmSyncStatus=unique_index_error_company
Other accepted values for crmSyncStatus: synced, not_synced, error, property_doesnt_exist, required_field_missing, company_already_exists_with_name, company_already_exists_with_linkedin_url.
Each company in the response now includes a crmSync block:
"crmSync": {
"provider": "hubspot",
"crmRecordId": "12345",
"syncDisabled": false,
"errors": [
{
"reason": "unique_index_error_company",
"metadata": {
"alreadyExistingCompanyId": "abc123"
}
}
]
}The alreadyExistingCompanyId is the lemlist company already occupying the conflicting CRM slot — this is the company you will remap contacts to in step 3.
💡 This field does the hard thinking for you. Instead of guessing which company is the correct one, the API tells you directly — it's the one already syncing to your CRM.
2. GET /api/contacts — filter by company
Four new mutually exclusive filters let you list all contacts attached to a given company. Pass any one of them:
companyId
companyDomain
companyLinkedinUrl
companySalesnavUrl
GET /api/contacts?companyId={companyId}The domain and URL variants resolve to a companyId internally. If no match is found, the endpoint returns an empty list — not a 404.
💡 These filters are also useful beyond duplicate cleanup: any team running account-based outreach can use them to pull all contacts for a given company domain without needing to know the internal ID.
3. DELETE /api/companies/:companyId
Deletes a lemlist company record. If contacts are still attached, the request is refused with a 400 COMPANY_HAS_CONTACTS error that includes a contactCount.
DELETE /api/companies/{companyId}Pass ?force=true to detach all attached contacts before deletion. Detached contacts have their companyId unset. The response includes unlinkedContacts: N confirming how many were detached. Use this when contacts don't need to be remapped — for example, if the duplicate contained test records or contacts you don't want to preserve.
DELETE /api/companies/{companyId}?force=trueThis endpoint only removes the lemlist record. It does not delete or modify the corresponding record in your CRM — any orphaned CRM records must be cleaned up separately on the CRM side.
Phase 2: Run the four-step cleanup workflow
Now that you know what each endpoint does, chain them together in sequence. These four API calls resolve every duplicate conflict automatically. Run them manually for a one-time cleanup, or schedule them nightly in n8n, Zapier, or Make.
This workflow assumes the lemlist company already mapped to the CRM is the correct one. The duplicate — the record throwing the error — is what gets deleted.
Step 1 — List all blocked companies
GET /api/companies?crmSyncStatus=unique_index_error_company
For each result, extract two values from the crmSync block:
_id — the duplicate (wrong) company to delete
errors[0].metadata.alreadyExistingCompanyId — the correct company to remap contacts to
You now have two IDs per conflict: the company to delete and the company to remap contacts to. A single API call gives you the full picture for every blocked company in your workspace.
Step 2 — List contacts attached to the duplicate
GET /api/contacts?companyId={WRONG_COMPANY_ID}This returns all contacts currently linked to the duplicate company.
Before remapping, you need a complete list of who's affected. Run this once per blocked company from step 1 — loop through each result and collect all the contacts to move.
Step 3 — Remap each contact to the correct company
For each contact returned in step 2, upsert by email and set companyId to the correct company:
POST /api/contacts
{
"email": "[email protected]",
"companyId": "{RIGHT_COMPANY_ID}"
}Remapping assigns each contact to the company already syncing to your CRM. After this step, the contact belongs to the correct account and will sync on the next CRM cycle.
Step 4 — Delete the now-empty duplicate
DELETE /api/companies/{WRONG_COMPANY_ID}With all contacts remapped, the duplicate has no attached records and can be removed cleanly. This resolves the domain conflict — future syncs for the correct company will complete without errors. If you skipped step 3, use ?force=true to detach and delete in one call, but note that detached contacts will have no company associated until you reassign them.
Practical Application
Here's what this looks like in practice. A B2B SaaS company's revenue operations team discovers that over 200 contacts are missing from HubSpot. After checking sync logs in Settings → Integrations → HubSpot → Logs, they find 14 companies flagged with UNIQUE_INDEX_ERROR_COMPANY — all sharing domains with companies already in their CRM following a bulk data import several months prior.
Instead of opening each duplicate in the lemlist UI and correcting it one at a time — which would have taken the better part of a day — they build a four-step n8n workflow:
Fetch all companies with crmSyncStatus=unique_index_error_company
For each blocked company, fetch its attached contacts
Remap each contact to the alreadyExistingCompanyId
Delete the now-empty duplicate
The first run resolves all 14 conflicts and unblocks all 200+ contacts, which appear in HubSpot within the next sync cycle. They schedule the workflow to run nightly. Over the following two weeks, it automatically catches and resolves three new conflicts — none of which the team would have noticed until a rep flagged missing data.
Troubleshooting and Pitfalls
DELETE returns 400 COMPANY_HAS_CONTACTS
Root cause: Contacts are still attached — step 3 didn't run or didn't complete for all contacts.
Re-run GET /api/contacts?companyId={WRONG_ID} to check how many contacts remain
Complete step 3 for any remaining contacts, then retry the DELETE
Alternatively, use DELETE /api/companies/{WRONG_ID}?force=true to detach and delete in one call — detached contacts will have no company until reassigned
alreadyExistingCompanyId is missing from the error response
Root cause: The conflict type is different. This field only appears for unique_index_error_company — other error reasons have different metadata.
Check errors[0].reason in the crmSync block
If the reason is not unique_index_error_company, this workflow does not apply to that record
Use crmSyncStatus=error as a broader filter to see all sync errors, then triage by reason
Contacts still not syncing after cleanup
Root cause: The contact may be linked to a different company that also has sync errors, or the CRM may need a few minutes to process.
Re-run GET /api/companies?crmSyncStatus=unique_index_error_company to check for additional conflicts
Wait a few minutes, then check sync logs in Settings → Integrations → [CRM] → Logs
If a contact was detached via ?force=true, reassign it using the contacts API
CRM still shows orphaned records after cleanup
Root cause: DELETE /api/companies only removes the lemlist record — it does not modify your CRM.
Log into HubSpot, Salesforce, or Pipedrive
Find the orphaned company record and delete or merge it using your CRM's tools
Use your CRM's built-in duplicate detection to identify and clean up similar records
