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

# client.bulk

> Use client.bulk in the Reverse Contact TypeScript SDK to enrich people in bulk, wait for the job, and read results as they come or as CSV or NDJSON.

- Last verified: 2026-11-04

Submit people for Bulk enrichment, then check progress, wait for the job to stop, cancel the job, read its results as they come or download them.

| Method | Endpoint | Cost |
| ------ | -------- | ---- |
| `client.bulk.enrichPersons()` | [Bulk enrichment](https://docs.reversecontact.com/endpoints/bulk-enrich-persons) | Free to send. Each item costs the same as its individual API call. Credits are reserved when the job is created and refunded for data that is not found. |
| `client.bulk.list()` | [Job history](https://docs.reversecontact.com/endpoints/jobs) | Free. |
| `client.bulk.retrieve()` | [Get a job](https://docs.reversecontact.com/endpoints/job) | Free even with a zero balance. Does not count against rate limits. |
| `client.bulk.results()` | [Download job results](https://docs.reversecontact.com/endpoints/job-results) | Free even with a zero balance. |
| `client.bulk.rows()` | [Read job results by page](https://docs.reversecontact.com/endpoints/job-rows) | Free even with a zero balance. Each page counts toward rate limits. |
| `client.bulk.iterateRows()` | [Read job results by page](https://docs.reversecontact.com/endpoints/job-rows) | Free even with a zero balance. Each page counts toward rate limits. |
| `client.bulk.cancel()` | [Cancel a job](https://docs.reversecontact.com/endpoints/job-cancel) | Free. Items that had not started are never charged. |
| `client.bulk.wait()` | [Get a job](https://docs.reversecontact.com/endpoints/job) | Free even with a zero balance. Does not count against rate limits. |
| `client.bulk.listAll()` | [Job history](https://docs.reversecontact.com/endpoints/jobs) | Free. |

Create `client` by following [Install and configure](https://docs.reversecontact.com/sdk/javascript/install). The examples use a saved `jobId` from the relevant job family. The `@reverse-contact/sdk` package exports `Body`, `Data`, `Query` and the other types used in the signatures.

JSON responses contain `data` and optional `quotas` and `metadata`. They also include the original `response`, with its body already read. See [First call](https://docs.reversecontact.com/sdk/javascript/first-call) for response handling.

HTTP errors use the [APIError subclasses and retry rules](https://docs.reversecontact.com/sdk/javascript/errors-and-retries): `BadRequestError`, `AuthenticationError`, `InsufficientCreditsError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `GoneError`, `UnprocessableEntityError`, `RateLimitError`, `DailyLimitError`, `KeyCreditLimitError`, `UnavailableForLegalReasonsError` and `InternalServerError`, according to the API response. Other statuses use `APIError`.

## enrichPersons()

Send 1 to 25,000 items. The SDK generates an `Idempotency-Key` if you omit it and reuses the key on automatic retries. Save your own key to repeat the same submission after a restart.

**Signature**

```typescript [TypeScript SDK]
client.bulk.enrichPersons(
  params: Body<"bulkEnrichPersons">,
  options?: IdempotentRequestOptions
): Promise<Result<Data<"bulkEnrichPersons">>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `params` **required** | `Body<"bulkEnrichPersons">` | Request body. See the fields and their requirements below. |
| `params.items` **required** | `Body<"bulkEnrichPersons">["items"]` | 1 to 25,000 people, see [Items](https://docs.reversecontact.com/endpoints/bulk-enrich-persons#items) |
| `params.families` | `("profile" \| "contact_email" \| "contact_phone")[]` | What to look up for each item: `profile`, `contact_email`, `contact_phone`. Defaults to `["profile"]`. |
| `params.emailType` | `string` | With `contact_email`, the kind of email to find: `professional` (default), `personal` or `any` |
| `params.live` | `boolean` | `true` fetches the profile of a Social profile URL item live. Omitted or `false`, saved data is used. |
| `params.name` | `string` | A name for the job, up to 255 characters, shown in the job history and in the app. Defaults to `Sent via API`. |
| `params.webhookUrl` | `string` | Public HTTPS URL that receives one `POST` when the job settles. See [Webhook callback](https://docs.reversecontact.com/endpoints/bulk-enrich-persons#webhook-callback) |
| `options` | `IdempotentRequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). Also accepts `idempotencyKey`. The SDK generates a key if you omit it. |

**Returns**

A response envelope with the background job in `data.job`. Save `data.job.id`, then call `client.bulk.wait(id)`. See [Lists and Bulk enrichment](https://docs.reversecontact.com/sdk/javascript/lists-and-bulk). This is not a `Job` object.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response.

**Example**

**Example**

```typescript [TypeScript SDK]
const created = await client.bulk.enrichPersons({
  items: [{ linkedinUrl: "https://social.com/in/janedoe", externalId: "crm-001" }],
  families: ["profile"]
}, { idempotencyKey: "crm-export-2026-11-04" });
console.log("Save job id:", created.data.job.id);
const progress = await client.bulk.wait(created.data.job.id);
console.log(progress.data.status);
```

## list()

Read background job history, including Bulk enrichment and List jobs allowed by the API key permissions.

**Signature**

```typescript [TypeScript SDK]
client.bulk.list(
  params?: Query<"listJobs">,
  options?: RequestOptions
): Promise<Page<Data<"listJobs">["items"][number], undefined>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `params` | `Query<"listJobs">` | Query parameters. Omit to use the defaults. |
| `params.type` | `string` | Filter by these comma-separated job types. See [Job types](https://docs.reversecontact.com/endpoints/jobs#job-types) |
| `params.status` | `string` | Filter by these comma-separated statuses: `queued`, `running`, `paused`, `completed`, `cancelled`, `failed` |
| `params.listId` | `string` | Return only jobs for this List (UUID) |
| `params.perPage` | `number` | Jobs per page, from 1 to 100. Defaults to 50. |
| `params.cursor` | `string` | The `nextCursor` of the previous page, sent with the same filters. A malformed cursor, or one sent with other filters, returns `400`. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

A `Page` with results in `data` and pagination through `nextCursor`, `hasNextPage()` and `getNextPage()`. The page also includes optional `quotas` and `metadata`, and the original `response`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response.

**Example**

**Example**

```typescript [TypeScript SDK]
const page = await client.bulk.list({ type: "enrich_persons", perPage: 20 });
console.log(page.data);
```

## retrieve()

Read the current state of a background job. Use `wait` to hold the request while the job is queued or running.

**Signature**

```typescript [TypeScript SDK]
client.bulk.retrieve(
  jobId: string,
  params?: Query<"getJob">,
  options?: RequestOptions
): Promise<Result<Data<"getJob">>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `params` | `Query<"getJob">` | Query parameters. Omit to use the defaults. |
| `params.wait` | `number` | Hold the request up to this many seconds, 0 to 20, while the job is queued or running. It answers as soon as the job settles or pauses, or with the job still running when the wait ends. Defaults to 0. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

`Result<BulkJob>` with the current status, progress, credit usage and result links in `data`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response.

**Example**

**Example**

```typescript [TypeScript SDK]
const result = await client.bulk.retrieve(jobId, { wait: 20 });
console.log(result.data.status, result.data.credits);
```

## results()

Download only when `links.results` is set, after the job completes or is cancelled. Failed jobs have no results file.

**Signature**

```typescript [TypeScript SDK]
client.bulk.results(
  jobId: string,
  params?: Query<"getJobResults">,
  options?: RequestOptions
): Promise<string>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `params` | `Query<"getJobResults">` | Query parameters. Omit to use the defaults. |
| `params.format` | `"csv" \| "ndjson"` | `csv` for the CSV file, `ndjson` for one `JobResultLine` JSON object per line. Defaults to `csv`. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

A `string`: the CSV without its byte order mark, or with `format: "ndjson"` one JSON object per line. Prepend `"\uFEFF"` when saving the CSV for spreadsheet applications.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options. `ConflictError` with `JOB_RESULTS_NOT_READY` means the file is not ready. `ConflictError` with `JOB_RESULTS_FORMAT_UNAVAILABLE` means the job was stored before NDJSON existed: download its CSV.

**Example**

**Example**

```typescript [TypeScript SDK]
const job = await client.bulk.retrieve(jobId);
if (job.data.links.results) {
  const csv = await client.bulk.results(jobId);
  const ndjson = await client.bulk.results(jobId, { format: "ndjson" });
  const items = ndjson.split("\n").filter(Boolean).map((line) => JSON.parse(line));
  console.log(csv.length, items.length);
}
```

## rows()

Read one page of results, in the order sent, from the start of the run. While the job runs, a page stops at the first item not finished: `data` is empty and `nextCursor` is set while the next item runs, so read it again after `Retry-After`. `nextCursor` is `null` once nothing more will come. Available from SDK 0.3.0.

**Signature**

```typescript [TypeScript SDK]
client.bulk.rows(
  jobId: string,
  params?: Query<"getJobRows">,
  options?: RequestOptions
): Promise<Page<PageItem<Data<"getJobRows">>, Interpretation<Data<"getJobRows">>>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `params` | `Query<"getJobRows">` | Query parameters. Omit to read from the first item. |
| `params.cursor` | `string` | `nextCursor` of the previous page, up to 512 characters. Without it, the page starts at the first item. |
| `params.limit` | `number` | Items per page, 1 to 100. Defaults to 100. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

A `Page` with `JobResultLine` items in `data`, `nextCursor` and `getNextPage()`, like `list()`. `extras.status` is the job status when the page was read. A page can hold fewer items than `limit`. The page also includes optional `quotas` and `metadata`, and the original `response`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response. `ConflictError` with `JOB_RESULTS_FORMAT_UNAVAILABLE` means the job was stored before reading by page existed: download its CSV with `results()`. `RateLimitError` with `JOB_ROWS_BUSY` means too many pages are being read at once: the SDK retries it after `Retry-After`, as [Errors and retries](https://docs.reversecontact.com/sdk/javascript/errors-and-retries) describes.

**Example**

**Example**

```typescript [TypeScript SDK]
const page = await client.bulk.rows(jobId, { limit: 100 });
for (const item of page.data) console.log(item.row, item.status);
console.log(page.extras.status, page.nextCursor);
```

## iterateRows()

Read the results one item at a time, as they become ready, whatever the job is doing. While the next item runs, the iterator waits `Retry-After` (2 seconds by default) and reads the same position again. It ends with the job, or when the job is paused, since nothing moves until someone resumes it: read on later with `rows()` and the last cursor. A cancelled job still finishes the items it had started, within the hour, and the iterator waits for them too. Available from SDK 0.3.0.

**Signature**

```typescript [TypeScript SDK]
client.bulk.iterateRows(
  jobId: string,
  params?: Query<"getJobRows">,
  options?: RequestOptions
): AsyncGenerator<JobResultLine, void, unknown>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `params` | `Query<"getJobRows">` | `cursor` to start after a page you already read, and `limit`, 1 to 100 items per page. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. The `signal` also stops the wait between pages. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

An `AsyncGenerator` of `JobResultLine` items, in the order sent. The `@reverse-contact/sdk` package exports the `JobResultLine` type.

**Errors**

The errors of `rows()`. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`.

**Example**

**Example**

```typescript [TypeScript SDK]
for await (const item of client.bulk.iterateRows(jobId)) {
  console.log(item.row, item.input.externalId, item.status, item.creditsConsumed);
}
```

## cancel()

Request a server-side stop. Items that have not started are not charged.

**Signature**

```typescript [TypeScript SDK]
client.bulk.cancel(
  jobId: string,
  options?: RequestOptions
): Promise<Result<Data<"cancelJob">>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `options` | `RequestOptions` | Optional per-request `timeout`, `maxRetries`, `headers` and `signal`. See [Client options](https://docs.reversecontact.com/sdk/javascript/client-options). |

**Returns**

A response envelope with the cancellation acknowledgement in `data`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response.

**Example**

**Example**

```typescript [TypeScript SDK]
const result = await client.bulk.cancel(jobId);
console.log(result.data);
```

## wait()

Wait while the background job is `queued` or `running`. A `paused` job returns control to your code even though the job can resume later.

**Signature**

```typescript [TypeScript SDK]
client.bulk.wait(
  jobId: string,
  options?: WaitOptions<BulkJob>
): Promise<Result<BulkJob>>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `jobId` **required** | `string` | Background job id from `created.data.job.id` or job history. |
| `options` | `WaitOptions<BulkJob>` | Optional overall `timeout`, `pollWait`, `onPoll`, `headers` and `signal`. See [Wait options](https://docs.reversecontact.com/sdk/javascript/timeouts). |

**Returns**

`Result<BulkJob>` when the status is `completed`, `cancelled`, `failed` or `paused`. A `failed` status does not raise `JobFailedError`. Check `data.status`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response. `JobTimeoutError` means the overall wait deadline has expired. An unsuccessful terminal state does not raise `JobFailedError`.

**Example**

**Example**

```typescript [TypeScript SDK]
const result = await client.bulk.wait(jobId, { timeout: 30 * 60 * 1000 });
console.log(result.data.status, result.data.links.results);
```

## listAll()

**Signature**

```typescript [TypeScript SDK]
client.bulk.listAll(
  params?: Query<"listJobs">,
  options?: ListIteratorOptions
): AsyncGenerator<Data<"listJobs">["items"][number], void, unknown>
```

**Parameters**

| Name | Type | Description |
| ---- | ---- | ----------- |
| `params` | `Query<"listJobs">` | Query parameters. Omit to use the defaults. |
| `params.type` | `string` | Filter by these comma-separated job types. See [Job types](https://docs.reversecontact.com/endpoints/jobs#job-types) |
| `params.status` | `string` | Filter by these comma-separated statuses: `queued`, `running`, `paused`, `completed`, `cancelled`, `failed` |
| `params.listId` | `string` | Return only jobs for this List (UUID) |
| `params.perPage` | `number` | Jobs per page, from 1 to 100. Defaults to 50. |
| `params.cursor` | `string` | The `nextCursor` of the previous page, sent with the same filters. A malformed cursor, or one sent with other filters, returns `400`. |
| `options` | `ListIteratorOptions` | Optional `maxResults`, a nonnegative safe integer. Omit to read all pages. Also accepts request options. |

**Returns**

An `AsyncGenerator` of individual results. The iterator requests pages as needed. A repeated cursor raises `ReverseContactError`.

**Errors**

Calls can raise the HTTP `APIError` subclasses listed above. Network failures or cancellation raise `APIConnectionError`, and attempt timeouts raise `APITimeoutError`. `ReverseContactError` reports invalid SDK options or an invalid successful JSON response. `ReverseContactError` also reports invalid `perPage` or `maxResults` values and repeated cursors.

**Example**

**Example**

```typescript [TypeScript SDK]
for await (const job of client.bulk.listAll({ type: "enrich_persons" })) {
  console.log(job.id, job.status);
}
```

## Related guides

[Lists and Bulk enrichment](https://docs.reversecontact.com/sdk/javascript/lists-and-bulk), [Timeouts and cancellation](https://docs.reversecontact.com/sdk/javascript/timeouts). Return to the [TypeScript SDK overview](https://docs.reversecontact.com/sdk/javascript).

## FAQ

### How do I start a Bulk enrichment job in TypeScript?

Call `client.bulk.enrichPersons()` with 1 to 25,000 items. Save `created.data.job.id` and use `client.bulk.wait()` to follow progress.

### Does client.bulk.wait throw when a Bulk job fails?

`client.bulk.wait()` returns jobs in `completed`, `cancelled`, `failed` or `paused` states. Check `result.data.status`. A failed state does not raise `JobFailedError`.

### When can I download Bulk results?

Call `client.bulk.results()` when `links.results` is set, after the job completes or is cancelled. The method returns CSV text, or NDJSON with `format: "ndjson"`.

### Can I read Bulk results before the job ends in TypeScript?

Yes, from SDK 0.3.0. `client.bulk.iterateRows()` yields each result as soon as it is ready, in the order sent, and `client.bulk.rows()` reads one page at a time with a cursor.
