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

# Job history

> Read every job of your workspace, newest first, whoever started it, from the API, an AI assistant or the app, with filters by type, status and List, for free.

- Endpoint: `GET https://api.reversecontact.com/v2/jobs`
- Credits: Free.
- Answer: synchronous.
- Available in: REST API, MCP tool `list_jobs`, TypeScript and Python SDKs, CLI command `rc bulk list`.
- Last verified: 2026-11-04

## Introduction

The Job history endpoint returns the background jobs of your workspace, newest first: the jobs of [Bulk enrichment](https://docs.reversecontact.com/endpoints/bulk-enrich-persons), and the jobs that run the same way in the app or through a [List](https://docs.reversecontact.com/guides/lists), such as a List import or a Search exported as a file. It covers every job, whoever started it: an API key, an AI assistant through the [MCP server](https://docs.reversecontact.com/guides/mcp), Ask or a member of your team in the app. Each item has the fields of [Get a job](https://docs.reversecontact.com/endpoints/job#response-structure).

This endpoint is **free** (0 credits), also at a zero balance. It counts toward your per-minute rate limit like any other call.

A key sees the jobs its permissions allow: Person Enrichment or Contact for the `enrich_persons` jobs, Lists for the List jobs, Search for the Search exports. A key with no matching permission gets an empty page.

## Authorization

| Name     | Type   | Required | Description                                                                                         |
| -------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `apikey` | string | Yes      | Your API key from the developer dashboard. Pass it via `Authorization: Bearer YOUR_API_KEY` header. |

## Query Parameters

| Name      | Type    | Required | Description                                                                                                                           |
| --------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `type`    | string  | No       | Only these types, comma-separated, see [Job types](#job-types)                                                                        |
| `status`  | string  | No       | Only these statuses, comma-separated: `queued`, `running`, `paused`, `completed`, `cancelled`, `failed`                               |
| `listId`  | string  | No       | Only the jobs of this List (UUID)                                                                                                     |
| `perPage` | integer | No       | Jobs per page, from 1 to 100. Defaults to 50.                                                                                         |
| `cursor`  | string  | No       | The `nextCursor` of the previous page, sent with the same filters. A malformed cursor, or one sent with other filters, returns `400`. |

The route is `GET /v2/jobs`. A `type` or `status` that is not in its list returns `400 JOB_INVALID_INPUT` with the accepted values.

### Job types

| `type`               | What the job does                                                                                                                         |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `enrich_persons`     | Looks up people and returns a CSV: [Bulk enrichment](https://docs.reversecontact.com/endpoints/bulk-enrich-persons), or a file enriched with Bulk Import in the app |
| `list_import`        | Imports Signals or a file into a List: [Import members and data](https://docs.reversecontact.com/endpoints/list-intake-signals)                                     |
| `list_enrich`        | Buys data for the members of a List                                                                                                       |
| `list_search_import` | Adds the results of a Search to a List                                                                                                    |
| `search_export`      | Exports the results of a Search as a file                                                                                                 |

Only an `enrich_persons` job has a results CSV at [Download job results](https://docs.reversecontact.com/endpoints/job-results). The members of a List job are read with [List members](https://docs.reversecontact.com/endpoints/list-members), and its `links.operation` is the [List operation](https://docs.reversecontact.com/endpoints/list-operation) it runs as.

## Response structure

| Field     | Type    | Description                                      |
| --------- | ------- | ------------------------------------------------ |
| `success` | boolean | `true` if the request was processed successfully |
| `error`   | `null`  | Always `null` on success                         |

**data** (JobPage): One page of jobs

| Field        | Type           | Description                                                                                                 |
| ------------ | -------------- | ----------------------------------------------------------------------------------------------------------- |
| `items`      | Job[]          | The jobs of this page, newest first, with the fields of [Get a job](https://docs.reversecontact.com/endpoints/job#response-structure) |
| `nextCursor` | string \| null | Send it back as `cursor` for the next page, `null` on the last page                                         |

`quotas` and `metadata` are the blocks of every V2 response: see [Usage](https://docs.reversecontact.com/endpoints/usage#response-structure).

## Errors

| Status | Code                | Meaning                                                                                                           |
| ------ | ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `400`  | `JOB_INVALID_INPUT` | An unknown `type` or `status`, a `perPage` out of range, or a cursor that is malformed or sent with other filters |
| `422`  | `VALIDATION_ERROR`  | `listId` is not a UUID                                                                                            |

## Code examples

**Code Examples**

```bash [cURL]
curl "https://api.reversecontact.com/v2/jobs?type=enrich_persons&status=completed,cancelled" \
  -H "Authorization: Bearer YOUR_API_KEY"
```
```javascript [JavaScript]
const headers = { "Authorization": "Bearer YOUR_API_KEY" };
const jobs = [];
let cursor = null;

// Read every completed or cancelled job, page by page.
do {
  const query = new URLSearchParams({ type: "enrich_persons", status: "completed,cancelled", perPage: "100" });
  if (cursor) query.set("cursor", cursor);
  const response = await fetch(`https://api.reversecontact.com/v2/jobs?${query}`, { headers });
  const { data } = await response.json();
  jobs.push(...data.items);
  cursor = data.nextCursor;
} while (cursor);

for (const job of jobs) console.log(job.name, job.status, job.credits.used);
```
```python [Python]
import requests

headers = {"Authorization": "Bearer YOUR_API_KEY"}
params = {"type": "enrich_persons", "status": "completed,cancelled", "perPage": 100}
jobs = []

while True:
    data = requests.get("https://api.reversecontact.com/v2/jobs", headers=headers, params=params).json()["data"]
    jobs.extend(data["items"])
    if not data["nextCursor"]:
        break
    params["cursor"] = data["nextCursor"]

for job in jobs:
    print(job["name"], job["status"], job["credits"]["used"])
```

## Example response

**Example Response**

```json [200 - Success]
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60",
        "type": "enrich_persons",
        "entity": "persons",
        "status": "completed",
        "source": "api",
        "name": "CRM export, November",
        "listId": null,
        "progress": { "total": 2, "processed": 2, "succeeded": 2, "failed": 0, "skipped": 0, "remaining": 0 },
        "credits": { "estimated": 9, "used": 6 },
        "estimatedSecondsRemaining": null,
        "error": null,
        "createdAt": "2026-11-04T09:30:00.000Z",
        "startedAt": "2026-11-04T09:30:01.000Z",
        "completedAt": "2026-11-04T09:31:12.000Z",
        "links": {
          "self": "/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60",
          "results": "/v2/jobs/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results",
          "operation": null
        }
      },
      {
        "id": "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
        "type": "list_import",
        "entity": "persons",
        "status": "running",
        "source": "dashboard",
        "name": "Trade show leads.csv",
        "listId": "3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40",
        "progress": { "total": 500, "processed": 120, "succeeded": 114, "failed": 6, "skipped": 0, "remaining": 380 },
        "credits": { "estimated": 0, "used": 456 },
        "estimatedSecondsRemaining": 240,
        "error": null,
        "createdAt": "2026-11-04T09:12:00.000Z",
        "startedAt": "2026-11-04T09:12:02.000Z",
        "completedAt": null,
        "links": {
          "self": "/v2/jobs/c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
          "results": null,
          "operation": "/v2/lists/3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40/operations/c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f/progress"
        }
      }
    ],
    "nextCursor": null
  },
  "error": null,
  "quotas": {
    "creditsConsumed": 0,
    "workspace": {
      "id": "ws_abc123",
      "credits": { "total": 10000, "used": 4528, "left": 5472 }
    }
  },
  "metadata": {
    "requestId": "527e1f57-3514-4080-a649-adb2983ffb0f",
    "executionTimeMs": 44
  }
}
```
