> Reverse Contact API documentation. This is the Markdown copy of https://docs.reversecontact.com/guides/lists
> Every page is listed at https://docs.reversecontact.com/llms.txt, every endpoint with its credit cost and status at https://docs.reversecontact.com/catalog.json

# Lists: orchestrate enrichment

> Orchestrate enrichment for up to 25,000 people or companies with Lists, including deduplication, credit budgets and stored results.

- Last verified: 2026-11-04

Lists are the centre of the Reverse Contact API. Every other endpoint is a single lookup: one person or one company in, one answer out, and your code chains the lookups, follows the async ones, retries, removes duplicates, counts credits and stores the results. A List orchestrates that whole pipeline for you, for up to 25,000 people or companies at a time. You hand it what you know about them: it identifies each one, buys the data you ask for under a budget you set, keeps the result, and tells you when it is done.

## What a List orchestrates

For each line you send, a List runs the chain you would otherwise write yourself:

1. **Identity.** It picks the lookup each Signal needs. A Social profile URL is read from saved profiles, or live with `live: true`. An email, or a name with its company, is reversed to the person behind it. A domain, a company page URL or a company id leads to the company. A Signal no one matches is kept as [Pending Intake](#pending-intake), for free.
2. **Deduplication.** A person or a company already on the List is recognised and costs nothing, and lines naming the same identity with the same `externalId` are merged into one.
3. **Data.** It buys the data families you ask for (profile, company details, work email, phone number) only for the members that lack them. The lookup that adds a member already stores its profile, or its company details on a companies List, and the email of an email Signal becomes the member's work email, for free. Data a member already holds is never bought twice, and an email or a phone number already looked for is not looked for again.
4. **Budget.** It never spends more than the `maxCredits` you set. Before a step that would cross it, or when your balance runs out, it pauses, keeps the work done, and waits for you to resume.
5. **Execution.** The import runs in the background as one List Operation: free progress reads with long polling, a webhook each time it stops moving, a result for each line with your own `externalId`, and a cancel that never charges the lines not started.
6. **Storage.** Members and their data stay in the List, ready to read, to find by your own id, to export as CSV, or to grow with the next import.

## One set of Lists for the app, your code and your agents

A List belongs to your workspace, not to the API. A List created with the API shows up in the app for your team. A List built in the app, from a Search or a file, can be read, filled and exported through the API. Ask, in the app, and your own agent, through the [MCP server](https://docs.reversecontact.com/guides/mcp), and your code, through the API or the [SDKs](https://docs.reversecontact.com/sdk/javascript), work on the same Lists with the same rules. However a member entered, it is one member, charged once.

## Single endpoints or a List

| What you have to handle    | With single endpoints                         | With a List                                                      |
| -------------------------- | --------------------------------------------- | ---------------------------------------------------------------- |
| Many people at once        | One request per person                        | Up to 25,000 lines in one import                                 |
| Signals of different kinds | Your code picks the endpoint for each row     | The List picks the lookup for each Signal                        |
| Emails and phone numbers   | One async job to follow per lookup            | One operation for the whole import                               |
| Duplicates                 | Your code detects them                        | A member already on the List is free                             |
| Spending                   | Your code counts credits                      | `maxCredits`, with pause and resume                              |
| Failures and retries       | Your code retries, and must not pay twice     | Send the import again with its `operationId`: nothing is charged twice |
| The result                 | Your code stores it                           | Kept in the List, shared with the app and your agents            |

A single endpoint stays the right tool to look up one person or one company while a user waits: [Enrich profile](https://docs.reversecontact.com/endpoints/enrich-person), [Person profile](https://docs.reversecontact.com/endpoints/fetch-profile) or [Find Email](https://docs.reversecontact.com/endpoints/contact-email). Work on a set of people or companies you keep (a CRM segment, a prospect list that grows every week, target accounts to hand to your sales team) belongs in a List.

## A List or Bulk enrichment

[Bulk enrichment](https://docs.reversecontact.com/guides/bulk-enrichment) also takes up to 25,000 people in one request, without a List: it looks up each one, gives you a CSV with one row per item, and keeps nothing else. Choose by what you do with the result:

| You want to                                              | Use     |
| -------------------------------------------------------- | ------- |
| Enrich a file of people once and get it back as CSV      | Bulk enrichment |
| Keep the people, add to them and buy data over time      | A List  |
| Never pay twice for someone you already have             | A List  |
| Work on companies, or buy company details                | A List  |
| Cap the spend with `maxCredits`, pause and resume        | A List  |

The [job history](https://docs.reversecontact.com/endpoints/jobs) lists both: your Bulk enrichment jobs and the jobs of your Lists, such as imports, with their progress and credits.

## The building blocks of a List

| Term               | What it means                                                                                                   |
| ------------------ | --------------------------------------------------------------------------------------------------------------- |
| **List**           | A named set of people (`persons`) or of companies (`companies`) in your workspace, never both                   |
| **Signal**         | What you know about someone: a Social profile URL, an email, a first and last name with a company, a company domain |
| **List Member**    | A person or a company a Signal matched, with the data bought for it and your `externalId`                       |
| **Pending Intake** | The Signals no one matched, kept on the List for free so you can see what was not found                         |
| **Data family**    | Data you buy for each member: `profile`, `company`, `contact_email`, `contact_phone`                            |
| **List Operation** | One run of work on a List, such as an import, with an id, a status and counters, followed at its `pollUrl` or told to your `webhookUrl` |

## The API flow, endpoint by endpoint

1. **Create the List** with [Create a List](https://docs.reversecontact.com/endpoints/list-create): a name, and `persons` or `companies`.
2. **Fill it with Signals.** [Add members](https://docs.reversecontact.com/endpoints/list-members-add) takes up to 1,000 Signals and answers when they are done. [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) queues up to 25,000 Signals or a public CSV file, and can buy data families for each member.
3. **Follow the import** with [Read a List operation](https://docs.reversecontact.com/endpoints/list-operation), the `pollUrl` of the import, or be told by its `webhookUrl`.
4. **Read the result** with [List members](https://docs.reversecontact.com/endpoints/list-members), or download it with [Export a List](https://docs.reversecontact.com/endpoints/list-export).

Every call of this flow is free. You pay only for the members it adds and the data it buys (see [What a List costs](#what-a-list-costs)). [Build your first List](https://docs.reversecontact.com/guides/build-a-list) runs this flow step by step, with cURL, JavaScript, Python and the SDKs.

The API key needs the `lists` permission. A call that spends credits also needs the permission of what it buys, as the matching endpoint would: `persons:enrich`, `companies:enrich` or `contact` (see [API key management](https://docs.reversecontact.com/guides/api-key-management)).

## Add members or run an import

| You want to                                              | Use                                                         |
| -------------------------------------------------------- | ----------------------------------------------------------- |
| Add a few hundred people and get the result in the reply | [Add members](https://docs.reversecontact.com/endpoints/list-members-add)             |
| Add more than 1,000 Signals, or a public CSV or Google Sheet | [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) |
| Buy emails, phone numbers, profiles or company details for each member | [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) with `families` |
| Cap what a run may spend                                 | [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) with `maxCredits` |
| Be told when the work stops moving                       | [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) with `webhookUrl` |

An import of a CSV file sends its public URL in `sourceUrl` instead of `signals`: up to 25,000 rows and 25 MiB, with the columns matched from their headers (Social URL, email, first name, last name, company, domain).

## Grow a List and buy data over time

A List is built for work that lasts: add a file today, buy emails for its members tomorrow, add the new leads of next week.

- **Add new people at any time.** Send them with [Add members](https://docs.reversecontact.com/endpoints/list-members-add) or an import: a person already on the List reads `already_present` and costs nothing.
- **Buy data later.** To buy data for members already on a List, import them again with `families`: their identity costs nothing, and only the data they lack is bought. An email or a phone number already looked for, found or not, is not looked for again.
- **Take people out** with [Remove members](https://docs.reversecontact.com/endpoints/list-members-remove), which also removes the data stored for them, and delete a List you no longer need with [Delete a List](https://docs.reversecontact.com/endpoints/list-delete).
- **Imports run in turn.** An import queued behind a paused one on the same List waits until you resume or cancel it (`stopReason: "blocked_by_paused_operation"`). Deleting a List returns `409 LIST_BUSY` while an operation is queued, running or paused on it, while the lines of a cancelled one still wait for their result, or while a Search of the app is still adding people to it.

## Link each member to your CRM with externalId

Send `externalId` on each Signal, the id of the line in your CRM or file. Each line result gives it back, the member keeps it, and [List members](https://docs.reversecontact.com/endpoints/list-members) finds a member by it (`?externalId=crm-001`). You never have to store our ids to find the member of a line. A member keeps the first `externalId` it received, and `externalId` is never used to find anyone.

## Pending Intake

A Signal no one matches does not become a member: the List keeps it as a **Pending Intake** line, free, so you can see what was not found and fix it. Rows of [Get a List](https://docs.reversecontact.com/endpoints/list) and [List members](https://docs.reversecontact.com/endpoints/list-members) carry `kind: "pending"` for those lines and `kind: "member"` for members. Pending Intake is never enriched, and [Export a List](https://docs.reversecontact.com/endpoints/list-export) writes its lines with what was sent. Send a Signal again once it is fixed: when it finds someone, the new member replaces its Pending Intake line. Dismiss lines with the `pendingIds` of [Remove members](https://docs.reversecontact.com/endpoints/list-members-remove).

## Control spend: maxCredits, pauses and resume

An import with `families` whose worst case is above 25 credits needs `maxCredits`, the most it may spend. The import pauses before a step that would cross it, keeps the work done, and reports `stopReason: "max_credits"` with the smallest ceiling that lets it continue in `nextStep.minimumMaxCredits`. A balance that runs out pauses it the same way, with `stopReason: "balance"`. An import sent without `maxCredits` may spend up to its worst case.

To resume, send the same import again, same `operationId`, with a higher `maxCredits` or after adding credits: nothing already done is charged twice. To stop an import for good, cancel it with [Cancel a List operation](https://docs.reversecontact.com/endpoints/list-operation-cancel): the lines that had not started are never charged. See [Pauses and how to resume](https://docs.reversecontact.com/endpoints/list-intake-signals#pauses-and-how-to-resume).

## What a List costs

| What                                  | Credits                                                               |
| ------------------------------------- | --------------------------------------------------------------------- |
| Create, read, export, remove, delete, follow and cancel | Free                                                |
| A new person                          | 1 from a Social profile URL (2 with `live: true`), 2 from an email or a name |
| A new company                         | 1 from a company page URL or a company id (2 when the page is read live), 2 from a domain |
| A member already on the List, a Signal no one matches | Free                                                  |
| `profile` family on a people List, `company` family on a companies List | Per member that lacks it, 1 when read from its Social profile or company page URL, 2 when found by name or domain (2 in the worst case). A member added from a Signal already holds it, for free |
| `company` family on a people List     | Per member, 1 from the company page URL of the person's current company, 2 from its domain (2 in the worst case) |
| `contact_email` family                | 3 per email found, free when none is found. The email of an email Signal is kept for free |
| `contact_phone` family                | 20 per phone number found, free when none is found                    |

## Limits per workspace and per List

| Limit                                  | Paid plan | Free trial |
| -------------------------------------- | --------- | ---------- |
| Lists per workspace                    | 200       | 1          |
| Members per List                       | 25,000    | 50         |
| Pending Intake lines per List          | 25,000    | 50         |
| Signals per [Add members](https://docs.reversecontact.com/endpoints/list-members-add) call | 1,000 | 1,000 |
| Signals or CSV rows per import         | 25,000    | 25,000     |

The Lists of a free trial that ends without a purchase are deleted 30 days after the trial ended.

The List calls count toward your per-minute and daily rate limits like any other call, with two exceptions: reading an operation is never counted, and the request that starts an import counts once on the daily limit and not on the per-minute one, whatever its number of lines. The lines of the import then take their share of your per-minute limit as they run, at most 80% of it, and wait when that share is taken. See [Rate limits](https://docs.reversecontact.com/guides/rate-limits#how-requests-are-counted).

## Error codes of the List endpoints

The List endpoints answer with the error envelope of every V2 endpoint (see [Error handling & retries](https://docs.reversecontact.com/guides/errors-retries)). Their own codes:

| Status | Code                                | Meaning                                                                 |
| ------ | ----------------------------------- | ----------------------------------------------------------------------- |
| `400`  | `LIST_INVALID_INPUT`                | A field is missing or does not fit the List: the message says which     |
| `400`  | `LIST_ENTITY_MISMATCH`              | A company sent to a people List, or a person to a companies List, with [Add members](https://docs.reversecontact.com/endpoints/list-members-add). An import marks the line `refused` instead. |
| `400`  | `WEBHOOK_URL_UNREACHABLE`           | The import `webhookUrl` is not a public HTTPS URL                       |
| `402`  | `INSUFFICIENT_CREDITS`              | The balance cannot pay for what [Add members](https://docs.reversecontact.com/endpoints/list-members-add) could add |
| `403`  | `INSUFFICIENT_PERMISSIONS`          | The key lacks `lists`, or the permission of what the call buys          |
| `404`  | `LIST_NOT_FOUND`                    | No List with this id in your workspace                                  |
| `404`  | `LISTS_NOT_ACTIVATED`               | The workspace has never had a List: create one first                    |
| `404`  | `LIST_OPERATION_NOT_FOUND`          | No operation with this id on this List                                  |
| `409`  | `LIST_LIMIT_REACHED`                | The workspace already has its maximum of Lists                          |
| `409`  | `LIST_MEMBER_LIMIT_REACHED`         | The List already holds its maximum of members                           |
| `409`  | `LIST_PENDING_INTAKE_LIMIT_REACHED` | The List already holds its maximum of Pending Intake lines              |
| `409`  | `LIST_BUSY`                         | The List you delete still has work on it: an operation queued, running or paused, the last lines of a cancelled one, or a Search of the app adding people. Wait for it or cancel it |
| `409`  | `LIST_OPERATION_NOT_CANCELLABLE`    | The operation already ended                                             |
| `409`  | `IDEMPOTENCY_KEY_REUSED`            | The `Idempotency-Key` already created a List of the other kind          |
| `422`  | `VALIDATION_ERROR`                  | A field has the wrong type or format, such as an id that is not a UUID  |
| `422`  | `BULK_JOB_CSV_PARSE_ERROR`          | The `sourceUrl` file of an import could not be read                     |
| `429`  | `LIST_EXPORT_IN_PROGRESS`           | The workspace is already exporting a List: retry after `Retry-After`    |
| `429`  | `LIST_EXPORT_BUSY`                  | The server is serving another export: retry after `Retry-After`         |
| `502`  | `LIST_JOB_ENGINE_ERROR`             | The import could not be queued: send it again with the same `operationId` |
| `503`  | `LISTS_PREPARING`                   | The first List of the workspace is being set up: retry in a few seconds |
| `503`  | `BULK_QUEUE_UNAVAILABLE`            | The import queue is busy: retry shortly                                 |
| `504`  | `GATEWAY_TIMEOUT`                   | The request ran out of time: see [Add members](https://docs.reversecontact.com/endpoints/list-members-add) and [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals) |

## FAQ

### What is a List in the Reverse Contact API?

A List is a named set of people, or of companies, kept in your Reverse Contact workspace, and the part of the API that orchestrates the work on them. From Signals (Social URLs, emails, names with a company, company domains) it identifies each person or company, skips those already on the List, buys the data families you ask for under a `maxCredits` budget and keeps the result, which you read with `GET /v2/lists/:listId/members` or download with `GET /v2/lists/:listId/export`. It is the same List your team sees in the app.

### When should I use a List instead of Enrich profile?

Use `POST /v2/enrich/persons` to look up one person while a user waits. Use a List to enrich a file or a CRM segment of hundreds or thousands of contacts, to buy emails or phone numbers for a set of people under a `maxCredits` cap, or to keep a set of prospects that grows over time: one List import takes up to 25,000 lines, runs in the background, and never charges twice for a person already on the List.

### How much does it cost to add someone to a List?

A new person costs 1 credit from a Social profile URL read from saved data, 2 with `live: true`, and 2 from an email or a name. A person already on the List and a Signal no one matches are free, and so are creating, reading, exporting and deleting Lists.

### How do I find emails for everyone on a List?

Send the members again to the import endpoint, `POST /v2/lists/:listId/intake/signals`, with `families: ["contact_email"]`. People already on the List cost nothing for their identity, each email found costs 3 credits, and a person without an email costs nothing. A person whose email was already looked for, found or not, is not looked up again, and the email of an email Signal becomes the person's work email for free. Above 25 credits of worst case, the import needs `maxCredits`.

### What happens to a Signal that matches no one in a List?

A Signal no one matches is kept on the List as a Pending Intake line, for free, and never enriched. Get a List and List members return those lines with `kind: "pending"`, and Export a List writes them with what was sent. Send the Signal again once it is fixed: when it finds someone, the new member replaces its Pending Intake line. Dismiss the lines you do not need with the `pendingIds` of `DELETE /v2/lists/:listId/members`.
