Skip to main content
Building a list and uploading one get contacts into Fuse. This page is what you do with a list afterwards. Everything here needs a key with the lists scope. Enrichment spends credits; the rest of this page, including CRM pushes, does not.

Enrich the contacts you have

POST /business/lists/{listId}/enrich fills in missing contact details for a list you already built. mode decides what runs:
limit is how many rows to take, in the list’s default order (oldest first) after any filters. It is required unless you name the contacts with contactIds (up to 500 row ids from the list). validate_emails and demographics process at most 5,000 rows: a larger limit is accepted, but only the first 5,000 rows are taken. Rows that already hold the data you asked for, or were searched for it before, are skipped and not charged, but they still count toward limit. So the same call made twice takes the same rows; to go further down the list, see Work through a long list. It answers 202 and works in the background. totalContacts is the number of rows this run takes (fewer than limit near the end of the list) and estimatedMinutes roughly how long it runs.

Follow the enrichment job

The three finding modes (email, phone, phone_and_email) run as a job, and the 202 carries its jobId and status:
  • pending: the job is waiting. Your enrichment jobs run one at a time, whether you started them here or in the app.
  • processing: the job is running.
Poll GET /business/jobs/{jobId} for its status, its counts and the credits it has charged so far. Long-running work shows the response and what each status means. Sending the same request again while its job is still queued or running (same mode, filters, offset and limit) returns that job instead of starting another. validate_emails and demographics do not return a job. Follow the list’s completionStatus instead.

Work through a long list

A call without offset always starts at the list’s first row. To work through a long list in batches, pass offset as the previous call’s offset plus its limit:
offset counts rows after filters, and works with the three finding modes only. It cannot be combined with contactIds.

If the balance runs out

Before a finding job starts, the balance must cover every contact it takes at the highest price it could cost: 50 per email (100 when personal emails are prioritised), 200 per phone, 250 for both. Otherwise the call answers 402 INSUFFICIENT_CREDITS. Most contacts then cost less, because you pay only for what is found. If credits still run out while the job runs (for example, because other work spent them meanwhile), the job pauses: its status turns stopped, with pausedReason: "INSUFFICIENT_CREDITS". Everything it found before the pause stays on the list. Top up, then resume it from the list’s page in the app. The API cannot resume or cancel a job.

Enrich only the rows you choose

The three finding modes also take filters, the same row filter GET /business/lists/{listId}/rows uses. This enriches only the rows a smart column marked as a fit:
filters and contactIds cannot be combined, and neither works with validate_emails or demographics. The qualify leads workflow uses this pattern end to end.
Enrichment bills per contact enriched, not per contact attempted: a contact whose email cannot be found does not cost. A work email costs 20 credits when Fuse’s own data finds it and 50 when a partner provider does, a phone 200, a re-verified email 10 and a demographics match 1. Contacts that already carry the data are never charged again. Credits has every price. A list with nothing left to find answers 409 ALL_CONTACTS_ALREADY_ENRICHED. Check the balance with GET /business/credits first for a large list.

Copy rows into another list

POST /business/lists/save-to-list copies rows from one list into another. sourceListId and limit are both required, so a copy is always bounded. The copy does not enrich: run POST /business/lists/{listId}/enrich on the destination afterwards if the rows need emails or phones.
Name an existing list with listId, or pass listName to create one. Add filters to copy only the rows that match — the same row filters GET /business/lists/{listId}/rows takes. The copy adds the same contacts to the destination rather than creating new ones, so anything you later enrich on either list shows on both. Custom and smart column values belong to the list they were written on and are not copied.

Turn contacts into companies

POST /business/lists/{listId}/derive-company-list builds a company list from the employers of a contact list. Send the new list’s name (required, 1 to 255 characters; a name already in use answers 409 LIST_NAME_TAKEN).
It answers 202 and fills the company list in the background, so follow the new list’s completionStatus:
sourceContacts is how many contacts were read, not how many companies came out — several contacts usually collapse into one company. Useful when a person-level search has found the right accounts and you now want to work them at company level.
A source list still building answers 409 LIST_PROCESSING. Wait for its completionStatus to settle and retry. A company list as the source answers 422 CONTACT_LIST_REQUIRED, and an empty one 422 LIST_EMPTY.

Commit an import

A file import is a two-step, and only the second step writes anything. POST /business/imports parses the file, answers the detected headers with a suggestedMapping, and stores that suggestion; it never commits, and a destination sent with it is refused. POST /business/imports/{importId}/commit runs the import into a list.
The mapping is settled for every header before the import runs: your mapping entry first, then the stored suggestion, then the standard header names (email, first_name, title, domain, url, position and the like). A value in mapping must be one of the template labels; anything else is 422 VALIDATION_FAILED rather than a silently dropped column. A file in which no column resolves to LinkedIn URL, Email, or a name plus Company Domain is refused with 422 IMPORT_IDENTITY_MISSING, because its rows could not be matched against the list.
The 202 echoes the mapping the import ran with and lists the ignoredHeaders it dropped. Its jobId is a UUID that GET /business/jobs/{jobId} cannot read — follow the destination list instead. A row that matches a contact already in the destination list (by LinkedIn URL, then email, then name plus company domain) is handled by onDuplicate: With enrich set to anything but none, merge and replace both enrich the existing contact in place, and skip leaves it out of the enrichment.
A commit is single-shot. A second POST /business/imports/{importId}/commit, including a retry that raced the first, answers 409 IMPORT_ALREADY_COMMITTED. To import the same file again, upload it again.

Push to your CRM

POST /business/lists/{listId}/crm-push sends a list’s contacts into a connected CRM. crm is required: hubspot, salesforce, zoho, attio or pipedrive.
It answers 202 and pushes in the background:
Pushing the whole list only fills empty fields on records that already exist in the CRM. Send contactIds (up to 500) to push only those contacts instead, which also overwrites fields on existing records. The CRM has to be connected already, or the call answers 422 CRM_NOT_CONNECTED; see CRM sync. A company list answers 422 CONTACT_LIST_REQUIRED.

Organise with folders

Folders group lists, campaigns or agents of one kind, and the kinds do not mix: contactList, companyList, campaign, agent.
Deleting a folder never deletes lists. They move back to the root, so a folder is safe to remove once you have stopped using it.

Fix one contact

Two calls work on a single contact rather than a whole list: PATCH /business/contacts/{contactId} sets which address or number is the primary one, when a contact has several:
GET /business/contacts/{contactId}/activities returns that contact’s timeline — what was sent, opened, replied and called, newest activity paged with limit and cursor:
It is the quickest way to answer “have we already talked to this person?” before adding them to another campaign.