> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fuseai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with lists

> Enrich, copy, derive, organise and push a list once it exists

[Building a list](/guides/build-a-list-from-search) and [uploading one](/guides/upload-your-contacts) get contacts into Fuse. This page is what you do with a list afterwards.

Everything here needs a key with the `lists` [scope](/concepts/scopes). 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:

| `mode` | Does |
| - | - |
| `email` | Finds work email addresses |
| `phone` | Finds phone numbers |
| `phone_and_email` | Both |
| `validate_emails` | Re-verifies the emails contacts already have |
| `demographics` | Resolves rows you added by hand to Fuse's canonical contact records |

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/$LIST_ID/enrich \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "mode": "phone_and_email", "limit": 200 }'
```

```json theme={null}
{
  "listIds": ["68a1f20b9c41d20014b3e901"],
  "mode": "phone_and_email",
  "totalContacts": 200,
  "estimatedMinutes": 10,
  "jobId": "68a2109c9c41d20014b41002",
  "status": "pending"
}
```

`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](#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](/concepts/long-running-work#following-an-enrichment-job) 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`:

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/$LIST_ID/enrich \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "mode": "email", "limit": 1000, "offset": 1000 }'
```

`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](/guides/smart-columns) marked as a fit:

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/$LIST_ID/enrich \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{
    "mode": "email",
    "limit": 1000,
    "filters": { "customColumns": { "68a1f3009c41d20014b3e9a1": { "dataType": "string", "include": ["yes"] } } }
  }'
```

`filters` and `contactIds` cannot be combined, and neither works with `validate_emails` or `demographics`. The [qualify leads workflow](/workflows/qualify-leads-before-enrichment) uses this pattern end to end.

<Note>
  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 2. Contacts that already carry the data are never charged again. [Credits](/concepts/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.
</Note>

## 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.

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/save-to-list \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "sourceListId": "'$SOURCE'", "listName": "Q3 shortlist", "limit": 500 }'
```

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`).

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/$LIST_ID/derive-company-list \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "name": "Q3 fintech companies" }'
```

It answers `202` and fills the company list in the background, so follow the new list's `completionStatus`:

```json theme={null}
{
  "list": { "id": "68a1f20b9c41d20014b3e977", "name": "Q3 fintech companies" },
  "sourceContacts": 412
}
```

`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.

<Warning>
  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`.
</Warning>

## Commit an import

An [upload](/guides/upload-your-contacts) is a two-step: send the file, then commit it once the column mapping is right.

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/imports/$IMPORT_ID/commit \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "listName": "Conference leads", "mapping": { "Email": "Email", "First": "First Name" } }'
```

The commit answers `202` with the destination `listId`. Its `jobId` is a UUID that `GET /business/jobs/{jobId}` cannot read — follow the destination list instead.

## 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`.

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/$LIST_ID/crm-push \
  -u "$FUSE_API_KEY:" -H "Content-Type: application/json" \
  -d '{ "crm": "hubspot" }'
```

It answers `202` and pushes in the background:

```json theme={null}
{ "crm": "hubspot", "listIds": ["68a1f20b9c41d20014b3e901"] }
```

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](/crm/overview). 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`.

| Call | Does |
| - | - |
| `GET /business/folders?type=contactList` | Lists the folders of that kind. `type` is required. |
| `POST /business/folders` | Creates one. Send `name`, and `type` unless it is a `contactList` folder. |
| `PATCH /business/folders/{folderId}?type=campaign` | Renames one. Send `name` in the body. Pass `type` in the query for a campaign or agent folder, and leave it out for a list folder. |
| `DELETE /business/folders/{folderId}?type=campaign` | Deletes the folder only. `type` works as it does on `PATCH`. |

<Note>
  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.
</Note>

## 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:

```json theme={null}
{ "contact": { "id": "68a1f2c89c41d20014b3e955", "primaryEmail": "ada@brightpay.com", "primaryPhone": "+14155552671" } }
```

`GET /business/contacts/{contactId}/activities` returns that contact's timeline — what was sent, opened, replied and called, newest activity paged with `limit` and `cursor`:

```json theme={null}
{
  "contactId": "68a1f2c89c41d20014b3e955",
  "activities": [{ "id": "68a2b1c09c41d20014b41a10", "channel": "email", "status": "replied" }]
}
```

It is the quickest way to answer "have we already talked to this person?" before adding them to another campaign.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.