> Reverse Contact API documentation. This is the Markdown copy of https://docs.reversecontact.com/endpoints/job-results
> 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

# Download job results

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

- Endpoint: `GET https://api.reversecontact.com/v2/jobs/:jobId/results`
- Credits: Free, also at a zero balance.
- Answer: synchronous.
- Available in: REST API, MCP tool `get_job_results`, TypeScript and Python SDKs, CLI command `rc bulk results`.
- Last verified: 2026-11-04

## Introduction

The Download job results endpoint returns the results of a job you [created](https://docs.reversecontact.com/endpoints/bulk-enrich-persons) 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](https://docs.reversecontact.com/endpoints/job) and download the file when `links.results` is set, or read the results as they come with [Read job results by page](https://docs.reversecontact.com/endpoints/job-rows).

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](https://docs.reversecontact.com/endpoints/enrich-person#response-structure) 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](https://docs.reversecontact.com/endpoints/enrich-person#response-structure). [Read job results by page](https://docs.reversecontact.com/endpoints/job-rows) 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**

```bash [cURL]
curl https://api.reversecontact.com/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o results.csv
```
```bash [cURL (NDJSON)]
curl "https://api.reversecontact.com/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results?format=ndjson" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o results.ndjson
```
```javascript [JavaScript]
import { 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);
}
```
```python [Python]
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"])
```
```python [Python (NDJSON)]
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**

```text [200 - CSV file]
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
```
```text [200 - NDJSON file]
{"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}}
```
```json [409 - Not ready]
{
  "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": "91574469-c62b-46b7-973a-16bc4211dfc8",
    "executionTimeMs": 15
  }
}
```
