Endpoints // Bulk
Follow a task with long polling until it settles, with its progress, the credits it reserved and spent, and the link to its results, for free.
Credits
Free, also at a zero balance, and never counted against the rate limits.
Introduction
The Get a task endpoint reads one task: it is the links.self path of a task you created. It says how far the task is, what it reserved and spent, and, once it is completed or cancelled, where to download its results. It reads any task of the task history too, such as a List import or a Search export started in the app.
Reading a task is free, also at a zero balance, and never counted against your rate limits, so you can follow a long task at the pace it asks for. A key reads the tasks its permissions allow: Person Enrichment or Contact for a Bulk enrichment task, Lists for a List task, Search for a Search export. A task the key may not read returns 404, as a task that does not exist.
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/tasks/:taskId.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
taskId | string | Yes | The id of the task (UUID) |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
wait | integer | No | Hold the request up to this many seconds, 0 to 20, while the task is queued or running. It answers as soon as the task settles or pauses, or with the task still running when the wait ends. Defaults to 0. |
How to follow a task
Read the task while its status is queued or running, with wait=20 and an HTTP client timeout above it, for example 30 seconds. While the task moves, the response carries a Retry-After: 2 header: start two reads at least that far apart. A held read already lasts longer, so the next one starts right away.
status | What it means |
|---|---|
queued | Waiting for its turn: read it again |
running | Items are being looked up: read it again |
paused | It waits to be resumed: a member of your workspace paused it in the app, or a List import reached its maxCredits or your balance |
completed | Every item was handled: download the results at links.results |
cancelled | Stopped: the items not started were never charged. The results of the items done are at links.results, where an item still in flight reads running until it ends, within the hour |
failed | The task could not run: error.code says why, and it has no results file |
completed, cancelled and failed are final. The webhook of a task is sent when it reaches one of them.
Response structure
| Field | Type | Description |
|---|---|---|
success | boolean | true if the request was processed successfully |
error | null | Always null on success |
dataobjectThe task
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
id | string | Task id (UUID) |
type | string | enrich_persons (Bulk enrichment, or a file enriched in the app), list_import, list_enrich, list_search_import or search_export, see the task history |
entity | string | persons or companies |
status | string | queued, running, paused, completed, cancelled or failed |
source | string | null | Who started it: api (an API key), mcp (an AI assistant), ask or dashboard (a person in the app). null for a task older than this field. |
taskName | string | The taskName given at creation, or the file name of a task started in the app |
listId | string | null | The List a List task works on, null otherwise |
estimatedSecondsRemaining | number | null | Seconds left at the recent pace while the task runs, null when unknown or settled |
error | object | null | { "code": "..." } when the task failed, null otherwise |
createdAt | string | Creation date (ISO 8601) |
startedAt | string | null | When it started running, null while queued |
completedAt | string | null | When it settled, null while it moves |
progressobjectHow far the task is
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
total | number | Items in the task, duplicates included |
processed | number | Items done |
succeeded | number | Items that found at least part of what was asked |
failed | number | Items that found nothing, or could not be looked up |
skipped | number | Items not run: a duplicate, no usable field, an invalid profile URL, not enough credits, or cancelled |
remaining | number | Items left to do |
creditsobjectWhat the task reserved and spent
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
estimated | number | Credits reserved when the task was created, 0 for a task that reserves nothing up front, such as a List import |
used | number | null | Credits spent. A List import counts them as it goes; any other task reads null until it has settled them, then gives back the rest of estimated. |
linksobjectWhere to go next
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
self | string | Path that reads this task |
results | string | null | Path of the results file, CSV or NDJSON, set once an enrich_persons task is completed or cancelled. See Download task results. To read the results while the task runs, see Read task results by page. |
operation | string | null | The List operation a List task runs as, null for other tasks |
quotas and metadata are the blocks of every V2 response: see Usage.
Errors
| Status | Code | Meaning |
|---|---|---|
404 | TASK_NOT_FOUND | No task with this id in your workspace, or one your key may not read |
422 | VALIDATION_ERROR | taskId is not a UUID, or wait is not an integer from 0 to 20 |
Code examples
Code Examples
curl "https://api.reversecontact.com/v2/tasks/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60?wait=20" \
-H "Authorization: Bearer YOUR_API_KEY"const taskId = "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60";
const headers = { "Authorization": "Bearer YOUR_API_KEY" };
let task;
do {
const response = await fetch(`https://api.reversecontact.com/v2/tasks/${taskId}?wait=20`, { headers });
task = (await response.json()).data;
} while (task.status === "queued" || task.status === "running");
console.log(task.status, `${task.credits.used} of ${task.credits.estimated} credits`, task.links.results);import requests
task_id = "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60"
headers = {"Authorization": "Bearer YOUR_API_KEY"}
while True:
task = requests.get(
f"https://api.reversecontact.com/v2/tasks/{task_id}",
headers=headers,
params={"wait": 20},
timeout=30
).json()["data"]
if task["status"] not in ("queued", "running"):
break
print(task["status"], task["credits"]["used"], task["links"]["results"])Example response
Example Response
{
"success": true,
"data": {
"id": "7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60",
"type": "enrich_persons",
"entity": "persons",
"status": "completed",
"source": "api",
"taskName": "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/tasks/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60",
"results": "/v2/tasks/7b1e4c2a-5d3f-4e8b-9a6c-1f2d3e4b5a60/results",
"operation": null
}
},
"error": null,
"quotas": {
"creditsConsumed": 0,
"workspace": {
"id": "ws_abc123",
"credits": { "total": 10000, "used": 4528, "left": 5472 }
}
},
"metadata": {
"requestId": "e5e6c816-df62-43a7-8fbe-1d3a94b6909c",
"executionTimeMs": 31
}
}{
"success": false,
"data": null,
"error": {
"code": "TASK_NOT_FOUND",
"message": "No task found with that identifier"
},
"quotas": {
"creditsConsumed": 0,
"workspace": {
"id": "ws_abc123",
"credits": { "total": 10000, "used": 4528, "left": 5472 }
}
},
"metadata": {
"requestId": "6408b12a-8668-464f-97f3-3a1497efb4f5",
"executionTimeMs": 12
}
}