New: API Reference docs are live — integrate Cleanlist enrichment into your apps. View API docs →
MCP API
Polling & Async

TL;DR: The MCP API is poll-based — it has no webhook endpoint. Async work returns a workflow_id; retrieve results by polling GET /enrichment/status/{workflow_id} until the status is terminal. Chain operations with the task_id cohort handle returned by searches and list reads. Status polls are free. (If you need push delivery, the legacy Public API offers webhooks on its enrich/bulk endpoint.)

Polling & Async Results

Several Cleanlist operations run in the background. This page explains how to retrieve their results.

The MCP API has no webhook / callback endpoint and no webhook_url request parameter — the supported pattern is polling. If you specifically need push delivery, the separate legacy Public API supports a webhook_url on its enrich/bulk endpoint with a delivery audit log.

Which operations are async?

OperationReturnsResult location
POST /enrichment/personworkflow_idthe target lead_list_id + inline result on terminal status
POST /enrichment/bulkworkflow_idthe enriched list
POST /enrichment/by-task (person)workflow_idthe target list (or the auto "MCP Results" list)
POST /lead-lists/{id}/csv-import (dispatching)workflow_id + enrichment_status_urlthe imported list
POST /smart-agents/runsmart_agent_task_idpoll GET /smart-agents/{id}

Synchronous operations — company enrichment, by-task (company), CRM/sequencer sync, exports — return their results directly and need no polling.

Polling enrichment status

GET /enrichment/status/{workflow_id}

Scope: enrich:read · Cost: free

Poll until status is completed, failed, or cancelled. The handle accepts either a workflow_id (enrich-..., bulk-enrich-...) or a cl-task_... cohort handle.

import time, requests
 
H = {"Authorization": "Bearer clapi_your_api_key"}
B = "https://api.cleanlist.ai/api/v1/public"
 
def wait_for(workflow_id, interval=5, timeout=900):
    deadline = time.time() + timeout
    while time.time() < deadline:
        s = requests.get(f"{B}/enrichment/status/{workflow_id}", headers=H).json()
        if s["status"] in ("completed", "failed", "cancelled"):
            return s
        time.sleep(interval)
    raise TimeoutError(workflow_id)
const B = "https://api.cleanlist.ai/api/v1/public";
const H = { Authorization: "Bearer clapi_your_api_key" };
 
async function waitFor(workflowId, interval = 5000, timeout = 900000) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    const s = await (await fetch(`${B}/enrichment/status/${workflowId}`, { headers: H })).json();
    if (["completed", "failed", "cancelled"].includes(s.status)) return s;
    await new Promise((r) => setTimeout(r, interval));
  }
  throw new Error(`timeout: ${workflowId}`);
}

See Enrichment → Status for the full response schema, including the inline result for single-person runs and the settled credits_charged / credits_refunded fields.

Polling tips

  • Free, but be reasonable. Status polls cost no credits. Poll every few seconds; back off for long bulk jobs.
  • Read the counters. total, processed, completed, and failed show live progress on bulk runs.
  • Watch refund_status. A terminal failure on a prepaid run sets refund_status: "pending_review" with a message to contact support — surface it to the user.
  • Bulk results aren't inline. For bulk and cohort runs, read the enriched leads from the target list via GET /lead-lists/{id}/leads once the workflow completes.

Chaining with task_id

Many responses carry a task_id in the envelope — a short-lived cohort handle. It's the glue for multi-step agent workflows:

  1. SearchPOST /search/people returns rows and a task_id for the result cohort.
  2. Enrich the cohort — pass that task_id to POST /enrichment/by-task without re-listing every lead_id. That returns a workflow_id.
  3. Poll — poll the workflow_id (or the cl-task_... handle) via GET /enrichment/status until terminal.

The same applies to GET /lead-lists/{id}/leads, which emits a task_id for the page so you can enrich a page of an existing list the same way.

Cohort handles are org-scoped and expire after roughly an hour. If a task_id has expired, re-run the originating search to obtain a fresh one.

Learn more