Skip to content

MaintenanceThe former company search endpoint goes into maintenance on November 9Migration guide

Integration guides

Lists: orchestrate enrichment

New

Last verified

A List orchestrates identity, deduplication, data and contact lookups for up to 25,000 people or companies at a time, under one budget, with one durable result shared with the app and your agents.

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, 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, and your code, through the API or the SDKs, 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 handleWith single endpointsWith a List
Many people at onceOne request per personUp to 25,000 lines in one import
Signals of different kindsYour code picks the endpoint for each rowThe List picks the lookup for each Signal
Emails and phone numbersOne async job to follow per lookupOne operation for the whole import
DuplicatesYour code detects themA member already on the List is free
SpendingYour code counts creditsmaxCredits, with pause and resume
Failures and retriesYour code retries, and must not pay twiceSend the import again with its operationId: nothing is charged twice
The resultYour code stores itKept 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, Person profile or Find 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 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 toUse
Enrich a file of people once and get it back as CSVBulk enrichment
Keep the people, add to them and buy data over timeA List
Never pay twice for someone you already haveA List
Work on companies, or buy company detailsA List
Cap the spend with maxCredits, pause and resumeA List

The job history 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

TermWhat it means
ListA named set of people (persons) or of companies (companies) in your workspace, never both
SignalWhat you know about someone: a Social profile URL, an email, a first and last name with a company, a company domain
List MemberA person or a company a Signal matched, with the data bought for it and your externalId
Pending IntakeThe Signals no one matched, kept on the List for free so you can see what was not found
Data familyData you buy for each member: profile, company, contact_email, contact_phone
List OperationOne 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: a name, and persons or companies.
  2. Fill it with Signals. Add members takes up to 1,000 Signals and answers when they are done. Import members and data 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, the pollUrl of the import, or be told by its webhookUrl.
  4. Read the result with List members, or download it with Export a List.

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). Build your first 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).

Add members or run an import

You want toUse
Add a few hundred people and get the result in the replyAdd members
Add more than 1,000 Signals, or a public CSV or Google SheetImport members and data
Buy emails, phone numbers, profiles or company details for each memberImport members and data with families
Cap what a run may spendImport members and data with maxCredits
Be told when the work stops movingImport members and data 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 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, which also removes the data stored for them, and delete a List you no longer need with Delete a List.
  • 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.

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 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 and List members carry kind: "pending" for those lines and kind: "member" for members. Pending Intake is never enriched, and Export a List 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.

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: the lines that had not started are never charged. See Pauses and how to resume.

What a List costs

WhatCredits
Create, read, export, remove, delete, follow and cancelFree
A new person1 from a Social profile URL (2 with live: true), 2 from an email or a name
A new company1 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 matchesFree
profile family on a people List, company family on a companies ListPer 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 ListPer member, 1 from the company page URL of the person's current company, 2 from its domain (2 in the worst case)
contact_email family3 per email found, free when none is found. The email of an email Signal is kept for free
contact_phone family20 per phone number found, free when none is found

Limits per workspace and per List

LimitPaid planFree trial
Lists per workspace2001
Members per List25,00050
Pending Intake lines per List25,00050
Signals per Add members call1,0001,000
Signals or CSV rows per import25,00025,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.

Error codes of the List endpoints

The List endpoints answer with the error envelope of every V2 endpoint (see Error handling & retries). Their own codes:

StatusCodeMeaning
400LIST_INVALID_INPUTA field is missing or does not fit the List: the message says which
400LIST_ENTITY_MISMATCHA company sent to a people List, or a person to a companies List, with Add members. An import marks the line refused instead.
400WEBHOOK_URL_UNREACHABLEThe import webhookUrl is not a public HTTPS URL
402INSUFFICIENT_CREDITSThe balance cannot pay for what Add members could add
403INSUFFICIENT_PERMISSIONSThe key lacks lists, or the permission of what the call buys
404LIST_NOT_FOUNDNo List with this id in your workspace
404LISTS_NOT_ACTIVATEDThe workspace has never had a List: create one first
404LIST_OPERATION_NOT_FOUNDNo operation with this id on this List
409LIST_LIMIT_REACHEDThe workspace already has its maximum of Lists
409LIST_MEMBER_LIMIT_REACHEDThe List already holds its maximum of members
409LIST_PENDING_INTAKE_LIMIT_REACHEDThe List already holds its maximum of Pending Intake lines
409LIST_BUSYThe 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
409LIST_OPERATION_NOT_CANCELLABLEThe operation already ended
409IDEMPOTENCY_KEY_REUSEDThe Idempotency-Key already created a List of the other kind
422VALIDATION_ERRORA field has the wrong type or format, such as an id that is not a UUID
422BULK_JOB_CSV_PARSE_ERRORThe sourceUrl file of an import could not be read
429LIST_EXPORT_IN_PROGRESSThe workspace is already exporting a List: retry after Retry-After
429LIST_EXPORT_BUSYThe server is serving another export: retry after Retry-After
502LIST_JOB_ENGINE_ERRORThe import could not be queued: send it again with the same operationId
503LISTS_PREPARINGThe first List of the workspace is being set up: retry in a few seconds
503BULK_QUEUE_UNAVAILABLEThe import queue is busy: retry shortly
504GATEWAY_TIMEOUTThe request ran out of time: see Add members and Import members and data

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.