Endpoints // Bulk
Download the results of a completed or cancelled job as one CSV or NDJSON file, one row per item in the order sent, with your externalId, what was found and what it cost, for free.
Credits
Free, also at a zero balance.
Introduction
The Download job results endpoint returns the results of a job you created as one file: one row per item, in the order you sent them, duplicates included, with your own fields and externalId first, then what was found for the item and what it cost. It is the links.results path of the job. By default the file is a CSV, the same file the app downloads; with format=ndjson, it holds one JSON object per item, for a program to read.
The file is ready once the job is completed or cancelled. Before that, and for a failed job, the request returns 409 JOB_RESULTS_NOT_READY. Follow the job with Get a job and download the file when links.results is set, or read the results as they come with Read job results by page.
A cancelled job gives the results of the items done, and the items it never started read skipped. It still finishes the items it had started, within the hour: until each one ends, its row reads running, and a file downloaded after that gives its outcome.
Downloading the results is free (0 credits), also at a zero balance, and as often as you need. It counts toward your per-minute rate limit like any other call. The key needs Person Enrichment or Contact, the permissions that read an enrich_persons job.
Authorization
| Name | Type | Required | Description |
|---|---|---|---|
apikey | string | Yes | Your API key from the developer dashboard. Pass it via Authorization: Bearer YOUR_API_KEY header. |
Request
The route is GET /v2/jobs/:jobId/results.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
jobId | string | Yes | The id of the job (UUID) |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
format | string | No | csv (default), or ndjson for one JSON object per line |
Response structure
The response is the file itself, not a JSON envelope, and a large file is streamed: read the body to its end. A Content-Disposition header names the file after the job, ending in _enriched.csv or _enriched.ndjson.
CSV
With format=csv, the default, the file is Content-Type: text/csv; charset=utf-8. It is UTF-8 and starts with a byte order mark, so a spreadsheet opens accented names correctly.
The columns depend on the families of the job, never on its rows, so every row has the same header:
| Columns | What they hold |
|---|---|
linkedinUrl, email, firstName, lastName, companyDomain, companyName, externalId | The item as you sent it |
rc_status | What happened to the item, see the table below |
rc_message | The same in a sentence, with the credits the item cost |
rc_email, rc_email_status | With contact_email: the email found, and found, not_found, error or skipped. With emailType: "any", rc_personal_email holds the personal email. |
rc_phone, rc_phone_type, rc_phone_status | With contact_phone: the number found, its type, and its status |
rc_id, rc_first_name, rc_headline and the other rc_ profile columns | With profile: one column per field of the Enrich profile response, nested fields joined with _, lists such as rc_experience as JSON. Without profile, only rc_linkedin_url, the profile the finders searched from. |
rc_status | Meaning |
|---|---|
enriched | Everything asked was found |
partial | Part of what was asked was found, such as the profile but no email |
not_found | Nothing was found; only a profile matched on the way to a finder is charged |
duplicate | The same person as an earlier item, looked up once on that row: free |
skipped | Not run: the item had no field, nothing the families can look up, or the job was cancelled before it. Free. |
insufficient_credits | Not run because your balance did not cover it when the job was created. Free. |
invalid_input | The item was rejected, such as an invalid Social profile URL. Free. |
data_subject_blocked | This person asked not to be processed under GDPR or CCPA. Free. |
rate_limited, error | The lookup could not be done after retries. Free: send the item again later. |
unknown | The lookup may have run but its result was not saved. It is not run again on its own. |
running | A cancelled job had started the item and it has not ended yet. It ends within the hour: download the file again for its outcome. |
NDJSON
With format=ndjson, the file is Content-Type: application/x-ndjson; charset=utf-8: one JSON object per item, one per line, in the order sent. Each object says what the CSV row says, typed, with the person in the shape of Enrich profile. Read job results by page returns the same objects, a page at a time.
| Field | Type | Description |
|---|---|---|
row | number | Position of the item in the job, from 1 |
input | object | The item as you sent it, externalId included, empty fields left out |
status | string | The rc_status of the item, see the table above |
message | string | The rc_message of the item, with the credits it cost |
creditsConsumed | number | Credits this item cost |
person | object | null | The person found: the full profile when isFullProfile is true. null when no one was found or the job did not ask for the profile. |
isFullProfile | boolean | true when person is the full profile |
linkedinUrl | string | Without profile in the job: the Social profile URL the finders searched from, as rc_linkedin_url |
contactEmail | object | With contact_email: status (found, not_found, error or skipped, null when the finder did not run), value, and personalValue with emailType: "any" |
contactPhone | object | With contact_phone: status (found, not_found, error or skipped, null when the finder did not run), value and type |
A job whose results were stored before NDJSON existed has its CSV only: format=ndjson returns 409 JOB_RESULTS_FORMAT_UNAVAILABLE.
Errors
The errors are JSON, in the envelope of every V2 response.
| Status | Code | Meaning |
|---|---|---|
404 | JOB_NOT_FOUND | No enrich_persons job with this id in your workspace, or one your key may not read. A List job or a Search export has no results file here. |
409 | JOB_RESULTS_NOT_READY | The job is still queued, running or paused, or it failed and has no file |
409 | JOB_RESULTS_FORMAT_UNAVAILABLE | format=ndjson on a job whose results were stored before NDJSON existed: download its CSV |
422 | VALIDATION_ERROR | jobId is not a UUID, or format is not csv or ndjson |
Code examples
Code Examples
curl https://api.reversecontact.com/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results \
-H "Authorization: Bearer YOUR_API_KEY" \
-o results.csvcurl "https://api.reversecontact.com/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results?format=ndjson" \
-H "Authorization: Bearer YOUR_API_KEY" \
-o results.ndjsonimport { writeFile } from "node:fs/promises";
const jobId = "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60";
const response = await fetch(`https://api.reversecontact.com/v2/jobs/${jobId}/results`, {
headers: { "Authorization": "Bearer YOUR_API_KEY" }
});
if (response.ok) {
await writeFile("results.csv", Buffer.from(await response.arrayBuffer()));
} else {
const { error } = await response.json();
console.log(error.code, error.message);
}import requests
job_id = "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60"
response = requests.get(
f"https://api.reversecontact.com/v2/jobs/{job_id}/results",
headers={"Authorization": "Bearer YOUR_API_KEY"},
stream=True
)
if response.ok:
with open("results.csv", "wb") as file:
for chunk in response.iter_content(chunk_size=65536):
file.write(chunk)
else:
print(response.json()["error"]["code"])import json
import requests
job_id = "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60"
response = requests.get(
f"https://api.reversecontact.com/v2/jobs/{job_id}/results",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"format": "ndjson"},
stream=True
)
response.raise_for_status()
for line in response.iter_lines():
if line:
item = json.loads(line)
print(item["row"], item["input"].get("externalId"), item["status"])Example response
The CSV below is cut after the first profile columns: the file carries every rc_ column of the profile. The NDJSON example cuts person after its first fields.
Example Response
linkedinUrl,email,firstName,lastName,companyDomain,companyName,externalId,rc_status,rc_message,rc_email,rc_email_status,rc_id,rc_public_id,rc_linkedin_url,rc_first_name,rc_last_name,rc_headline
https://www.social.com/in/janedoe,,,,,,crm-001,enriched,Profile and email found (4 credits),jane.doe@acme.com,found,prs_9f8e7d6c5b4a,janedoe,https://www.social.com/in/janedoe,Jane,Doe,VP of Marketing @ Acme Corp
,,John,Smith,acme.com,,crm-002,partial,"Profile found, no email (2 credits)",,not_found,prs_1a2b3c4d5e6f,john-smith,https://www.social.com/in/john-smith,John,Smith,Head of Sales at Acme{"row":1,"input":{"linkedinUrl":"https://www.social.com/in/janedoe","externalId":"crm-001"},"status":"enriched","message":"Profile and email found (4 credits)","creditsConsumed":4,"person":{"id":"prs_9f8e7d6c5b4a","firstName":"Jane","lastName":"Doe","linkedinUrl":"https://www.social.com/in/janedoe","headline":"VP of Marketing @ Acme Corp"},"isFullProfile":true,"contactEmail":{"status":"found","value":"jane.doe@acme.com","personalValue":null}}
{"row":2,"input":{"firstName":"John","lastName":"Smith","companyDomain":"acme.com","externalId":"crm-002"},"status":"partial","message":"Profile found, no email (2 credits)","creditsConsumed":2,"person":{"id":"prs_1a2b3c4d5e6f","firstName":"John","lastName":"Smith","linkedinUrl":"https://www.social.com/in/john-smith","headline":"Head of Sales at Acme"},"isFullProfile":true,"contactEmail":{"status":"not_found","value":null,"personalValue":null}}{
"success": false,
"data": null,
"error": {
"code": "JOB_RESULTS_NOT_READY",
"message": "The job is running: its results file is ready once it is completed or cancelled"
},
"quotas": {
"creditsConsumed": 0,
"workspace": {
"id": "ws_abc123",
"credits": { "total": 10000, "used": 4531, "left": 5469 }
}
},
"metadata": {
"requestId": "req_r1e2s3u4l5t6",
"executionTimeMs": 15
}
}