Skip to content

MaintenanceThe former company search endpoint goes into maintenance on November 9Migration guide

Endpoints // Lists

Read a List operation

New

Last verified

Available inAPIMCPSDK

Follow an import with long polling, read why it paused and what to do, and page through its per-line results, for free.

GET/v2/lists/:listId/operations/:operationId/progress

Credits

Free, also at a zero balance, and never counted against the rate limits.

Introduction

The Read a List operation endpoint reads an operation started by an import: it is the pollUrl of the import. It says how far the operation is, what it has charged, and what to do next with clientAction. With rows=true, it also returns the result of each line you sent, with your externalId and the member it became.

Reading an operation is free, also at a zero balance, and never counted against your rate limits, so you can follow a long import at the pace it asks for. Any key of the workspace with the lists permission can read it.

Authorization

NameTypeRequiredDescription
apikeystringYesYour API key from the developer dashboard. Pass it via Authorization: Bearer YOUR_API_KEY header.

Request

The route is GET /v2/lists/:listId/operations/:operationId/progress.

Path parameters

NameTypeRequiredDescription
listIdstringYesList id (UUID)
operationIdstringYesThe operationId of the import acknowledgement

Query Parameters

NameTypeRequiredDescription
waitintegerNoHold the request up to this many seconds, 0 to 20, while the operation runs. It answers as soon as the operation settles, pauses or stops, or with the operation still running when the wait ends. Defaults to 0.
rowsstringNotrue adds one page of per-line results
perPageintegerNoWith rows=true: lines per page, from 1 to 1,000. Defaults to 200.
cursorstringNoWith rows=true: the rows.nextCursor of the previous page

cursor and perPage without rows=true return 400 LIST_INVALID_INPUT.

How to follow an operation

Read the operation while clientAction is poll, with wait=20 and an HTTP client timeout above it, for example 30 seconds. Start two reads at least pollAfterSeconds apart (the Retry-After header carries the same value): a held read already lasts longer, so the next one starts right away. Then act on clientAction:

clientActionWhat to do
pollThe operation runs: read it again
doneIt ended. With completed, the data can be read in the List; read rows for the line results
resumeIt paused: stopReason says why and nextStep what to do, see Pauses and how to resume
reconcileThe outcome is unknown: read the List before doing anything, and never send the import again blindly

Response structure

FieldTypeDescription
successbooleantrue if the request was processed successfully
errornullAlways null on success
dataobject

The operation

Show child attributes
FieldTypeDescription
operationIdstringOperation id
listIdstringList id
kindstringlist_intake_persons or list_intake_companies
statusstringqueued, running, paused, stopped_budget, completed, partial, failed, cancelled, refused or unknown
clientActionstringpoll, resume, done or reconcile: what to do next
creditsConsumednumberCredits charged so far
creditsPendingnumberCredits reserved by steps in flight
creditsEstimatednumberCredits reserved when the operation was created: 0 for an import, which spends up to maxCredits
maxCreditsnumber | nullThe ceiling of the operation
estimatedSecondsnumber | nullSeconds left while it runs, 0 once done, null when not known yet
estimateBlockedUntilstring | nullWhen the daily extraction limit holds the operation: the next 00:00 UTC, when it resumes
pollAfterSecondsnumberSeconds to wait before reading again, 0 once you can stop
pollUrlstringPath of this endpoint for the operation
followUpobjectThe same follow-up for an agent: tool (get_list_operation, the MCP tool), its arguments (listId, operationId) and pollAfterSeconds
replayobjectAlways { "reuseOperationId": true, "startNewWork": false, "debitAgain": false }: sending the import again with the same operationId starts no new work and charges nothing twice
stopReasonstring | nullWhy it is not running: max_credits, balance, blocked_by_paused_operation, paused_by_user (paused from the app), list_member_limit, list_pending_intake_limit, insufficient_credits or cancelled
errorMessagestring | nullWhat failed, when it failed
startedAtstring | nullStart date (ISO 8601)
completedAtstring | nullEnd date (ISO 8601), once the data can be read
createdAtstring | nullDate the operation was created (ISO 8601)
updatedAtstring | nullDate of its last change (ISO 8601)
progressobject

Lines of the operation

Show child attributes
FieldTypeDescription
totalnumberLines in the operation
processednumberLines processed
succeedednumberLines that found their member and every family asked for
failednumberLines that failed, matched no one, or found their member but not a family asked for (an email not found, for example)
skippednumberLines skipped
remainingnumberLines left
unknownCountnumberLines whose outcome is unknown
nextStepobject

What to do after a stop, null otherwise

Show child attributes
FieldTypeDescription
actionstringraise_max_credits_and_resume, top_up_and_resume, resume (after a pause from the app), resume_or_cancel_blocking_operation or none
messagestringThe next step, in a sentence you can show
requiredCreditsnumberWorst-case credits of the step that stopped
minimumMaxCreditsnumberAfter a max_credits stop: the smallest maxCredits that lets it continue
blockingOperationIdstringThe paused operation this one waits for
rowsobject

With rows=true, one page of line results; absent without rows=true

Show child attributes
FieldTypeDescription
nextCursorstring | nullSend it back as cursor for the next page, null on the last page
itemsobject[]

One entry per line, in request order

Show child attributes
FieldTypeDescription
indexnumber | nullPosition of the line in signals, or among the non-empty data rows of a sourceUrl file, from 0; null for a file sent from the app
rowNumbernumberLine number in the operation
outcomestringpending (not processed yet), added, already_present, resolved (the member was found, but the line does not say whether it added it: after a restart, or when a requested family could not be bought), not_found, refused, failed, not_reached (not started before a cancel) or unknown
externalIdstring | nullYour identifier, as sent
memberIdstring | nullThe member the line names
errorCodestring | nullWhat went wrong: for example NOT_FOUND, ENTITY_MISMATCH (a company on a people List, or the reverse), DATA_SUBJECT_BLOCKED (the person asked us to stop processing their data), or CONTACT_EMAIL_NOT_FOUND when the member was found but a family could not be bought
reasonstring | nullHow the line ended, once it ended: completed when every requested family was bought, not_found, data_subject_blocked, unknown, or what the family lookup returned. null for a line without families that found its member.
signalobjectThe identifiers the line was sent with

quotas and metadata are the blocks of every V2 response: see Usage. quotas.creditsConsumed is 0 for the read itself.

An operation that does not belong to this List and workspace returns 404 LIST_OPERATION_NOT_FOUND.

Code examples

Code Examples

curl "https://api.reversecontact.com/v2/lists/3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40/operations/c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f/progress?wait=20" \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

The second example reads the import of Import members and data once done: Jane Doe is added (1 credit) and her email is found (3 credits). John Smith is already on the List, and the email of his Signal becomes his work email, free.

Example Response

{
  "success": true,
  "data": {
    "operationId": "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
    "listId": "3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40",
    "kind": "list_intake_persons",
    "status": "stopped_budget",
    "clientAction": "resume",
    "progress": {
      "processed": 312,
      "total": 500,
      "succeeded": 298,
      "failed": 14,
      "skipped": 0,
      "remaining": 188,
      "unknownCount": 0
    },
    "creditsConsumed": 998,
    "creditsPending": 0,
    "creditsEstimated": 0,
    "maxCredits": 1000,
    "estimatedSeconds": null,
    "estimateBlockedUntil": null,
    "pollAfterSeconds": 0,
    "pollUrl": "/v2/lists/3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40/operations/c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f/progress",
    "followUp": {
      "tool": "get_list_operation",
      "arguments": {
        "listId": "3f6c2a8e-1b4d-4f0a-9c7e-5d2b8a1e6f40",
        "operationId": "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f"
      },
      "pollAfterSeconds": 0
    },
    "replay": { "reuseOperationId": true, "startNewWork": false, "debitAgain": false },
    "stopReason": "max_credits",
    "nextStep": {
      "action": "raise_max_credits_and_resume",
      "message": "Paused at the operation's maxCredits ceiling. Resume it with a maxCredits of at least 1001 to continue. Work already done is kept and is not charged again.",
      "minimumMaxCredits": 1001,
      "requiredCredits": 3
    },
    "errorMessage": null,
    "startedAt": "2026-10-28T09:16:00.000Z",
    "completedAt": null,
    "createdAt": "2026-10-28T09:15:58.000Z",
    "updatedAt": "2026-10-28T09:19:12.000Z"
  },
  "error": null,
  "quotas": {
    "creditsConsumed": 0,
    "workspace": {
      "id": "ws_abc123",
      "credits": { "total": 10000, "used": 5520, "left": 4480 }
    }
  },
  "metadata": {
    "requestId": "req_o1p2e3r4a5t6",
    "executionTimeMs": 26
  }
}