Integration guides
Enrich a CSV file
NewUpload a CSV of contacts in the app and download it enriched, or loop over its rows with the API.
- Last verified
You have a file of contacts and want it back with profiles, work emails or phone numbers. There are two ways to do it:
- In the app, no code: Bulk Import takes your CSV and gives you an enriched CSV back.
- With the API: your code reads the file and calls an endpoint for each row. The API takes JSON, one person per request: there is no endpoint that accepts a CSV file.
Bulk Import in the app
- Upload your file. Open Bulk Import and drop a CSV of up to 25,000 rows and 25 MB. You can also paste a list of emails, LinkedIn profile URLs or names instead of uploading a file.
- Map your columns. Tell the import which columns hold the LinkedIn profile URL, the email, the first and last name, the company name and the company domain. Common headers such as
linkedin_url,emailorprofile_urlare detected for you, and you can change any mapping. - Choose what to look up. Each row can get its full profile, a work email and a phone number. Select one or several.
- Review and start. The review step counts the rows that will be enriched, the duplicates and the rows with no usable input, and shows the credits the import reserves before it starts.
- Download the result. The job page follows the import row by row. When it completes, you receive an in-app notification and an email, and the enriched CSV is ready to download.
A row needs at least one usable input: a LinkedIn profile URL, an email, or a name with its company. A row that repeats an earlier one is not enriched or charged twice.
What the enriched file contains
The result keeps every column of your file and adds these columns to each row:
| Column | Content |
|---|---|
rc_status | enriched, partial, not_found, invalid_input, skipped, insufficient_credits or error |
rc_message | Why a row was not enriched, in plain English |
rc_* profile columns | The fields of the Enrich profile response, one column per field (rc_first_name, rc_current_position_title, rc_location_city...). List fields such as rc_experience hold JSON. |
rc_email, rc_email_status | The work email, when you asked for one |
rc_phone, rc_phone_type, rc_phone_status | The phone number, when you asked for one |
partial means some of the requested lookups found a result and others did not. When you ask for contact details without the profile, a single rc_linkedin_url column replaces the profile columns. The file is UTF-8 and opens directly in Excel.
What it costs
You pay only for rows that return a result. A row that is not found, invalid, a duplicate or skipped costs nothing.
| Lookup | Credits per row |
|---|---|
| Profile from a LinkedIn profile URL | 1 when found |
| Profile from a LinkedIn profile URL, with Live enrichment | 2 when found |
| Profile from an email or a name | 2 when found |
| Work email | 3 when found |
| Phone number | 20 when found |
If your balance does not cover every row, the import takes the rows it can afford in file order and marks the others insufficient_credits. Top up and upload those rows again to enrich them.
Live enrichment applies to rows with a LinkedIn profile URL: it scrapes each profile fresh instead of reading the cached profile, and a large file can take several minutes. Bulk Import works during the free trial with your trial credits, except Live enrichment.
Enrich a CSV with the API
Read the file in your code and call, for each row, the endpoint that matches what the row holds:
| The row holds | Endpoint | Credits |
|---|---|---|
| A LinkedIn profile URL | POST /v2/fetch/persons | 1 when found |
| An email, or a name with its company | POST /v2/enrich/persons | 2 when found |
| You need a work email | POST /v2/contact/email | 3 when found |
| You need a phone number | POST /v2/contact/phone | 20 when found |
The example below reads contacts.csv, enriches each row from its email column with POST /v2/enrich/persons, and writes contacts_enriched.csv with five profile columns added. A 404 means no match and costs nothing. A 429 means the per-minute rate limit is reached: the example waits the number of seconds in error.details.retryAfter, then retries the same row.
Enrich a CSV
// npm install csv-parse csv-stringify
import { readFileSync, writeFileSync } from "node:fs";
import { parse } from "csv-parse/sync";
import { stringify } from "csv-stringify/sync";
const KEY = "YOUR_API_KEY";
const FIELDS = ["firstName", "lastName", "currentPositionTitle", "currentCompanyName", "linkedinUrl"];
async function enrich(email) {
while (true) {
const response = await fetch("https://api.reversecontact.com/v2/enrich/persons", {
method: "POST",
headers: { "Authorization": `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ email })
});
if (response.status === 429) {
const { error } = await response.json();
await new Promise((resolve) => setTimeout(resolve, error.details.retryAfter * 1000));
continue;
}
if (response.status === 404) return null;
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return (await response.json()).data.person;
}
}
const rows = parse(readFileSync("contacts.csv"), { columns: true, bom: true });
for (const row of rows) {
const person = row.email ? await enrich(row.email) : null;
for (const field of FIELDS) row[field] = person?.[field] ?? "";
}
writeFileSync("contacts_enriched.csv", stringify(rows, { header: true }));# pip install requests
import csv
import time
import requests
KEY = "YOUR_API_KEY"
FIELDS = ["firstName", "lastName", "currentPositionTitle", "currentCompanyName", "linkedinUrl"]
def enrich(email):
while True:
response = requests.post(
"https://api.reversecontact.com/v2/enrich/persons",
headers={"Authorization": f"Bearer {KEY}"},
json={"email": email},
)
if response.status_code == 429:
time.sleep(response.json()["error"]["details"]["retryAfter"])
continue
if response.status_code == 404:
return None
response.raise_for_status()
return response.json()["data"]["person"]
with open("contacts.csv", newline="", encoding="utf-8-sig") as source, \
open("contacts_enriched.csv", "w", newline="", encoding="utf-8") as target:
reader = csv.DictReader(source)
writer = csv.DictWriter(target, fieldnames=reader.fieldnames + FIELDS)
writer.writeheader()
for row in reader:
person = enrich(row["email"]) if row["email"] else None
for field in FIELDS:
row[field] = (person or {}).get(field) or ""
writer.writerow(row)For a row with a LinkedIn profile URL, call POST /v2/fetch/persons with {"url": ...} instead: it costs 1 credit instead of 2. Work emails and phone numbers are asynchronous: send every row first, keep the webhookId of each request, then collect the results by polling or through webhooks. See Rate limits & credits to size a large file against your per-minute limit.
FAQ
Can I upload a CSV file to the Reverse Contact API?
No. The Reverse Contact API takes JSON, one person per request, and has no endpoint that accepts a CSV file. To enrich a CSV without code, use Bulk Import in the app: upload the file, map its columns and download the enriched CSV. With the API, read the file in your code and call POST /v2/enrich/persons or POST /v2/fetch/persons for each row.
How many rows can Bulk Import enrich at once?
Bulk Import accepts a CSV of up to 25,000 rows and 25 MB per import. Split a larger file into several imports. Each row needs a LinkedIn profile URL, an email, or a name with its company.
How much does it cost to enrich a CSV file?
Bulk Import charges only rows that return a result: 1 credit for a profile found from a LinkedIn profile URL, 2 credits for a profile found from an email or a name, 3 credits for a work email found and 20 credits for a phone number found. Rows that are not found, invalid or duplicated cost nothing.
What does the enriched CSV contain?
The enriched CSV from Bulk Import keeps every column of the original file and adds rc_status, rc_message and one rc_* column per profile field, plus rc_email and rc_phone columns when a work email or a phone number was requested. rc_status tells whether each row was enriched, not found, invalid or skipped.
What happens when my credits run out during a Bulk Import?
Bulk Import reserves credits before the job starts. If the balance does not cover every row, Bulk Import enriches the rows it can afford in file order and marks the other rows insufficient_credits in the enriched CSV, without charging them. Top up and upload those rows again to enrich them.