New: API Reference docs are live — integrate Cleanlist enrichment into your apps. View API docs →
API Reference
Enrichment

TL;DR: POST /public/enrich/bulk accepts up to 250 contacts per request and returns a workflow_id immediately. Track progress via GET /public/enrich/status or supply a webhook_url to receive the full result set when the workflow finishes.

Enrichment

The Public API exposes two enrichment endpoints:

MethodPathPurpose
POST/api/v1/public/enrich/bulkSubmit a batch of contacts for enrichment
GET/api/v1/public/enrich/statusPoll workflow or task status

Behind the scenes, every enrichment runs through Cleanlist's waterfall provider system, which tries multiple providers in sequence until it finds reliable data.

Bulk enrichment

POST /api/v1/public/enrich/bulk

Submit up to 250 contacts per request. The endpoint validates the input, queues a Temporal workflow, and returns immediately.

curl -X POST https://api.cleanlist.ai/api/v1/public/enrich/bulk \
  -H "Authorization: Bearer clapi_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "enrichment_type": "partial",
    "webhook_url": "https://your-app.com/webhooks/cleanlist",
    "contacts": [
      {
        "first_name": "John",
        "last_name": "Doe",
        "company_domain": "acme.com"
      },
      {
        "linkedin_url": "https://www.linkedin.com/in/janedoe"
      },
      {
        "first_name": "Sam",
        "last_name": "Lee",
        "company_name": "Globex"
      }
    ]
  }'

Request body

FieldTypeRequiredDescription
enrichment_typestringNo (defaults to "partial")One of none, partial, phone_only, full, prospecting_only — see Enrichment Types
webhook_urlstring (URL)NoIf supplied, Cleanlist POSTs the full result set here when the workflow completes — see Webhooks
contactsarrayYes1–250 contact objects

Contact object

Each contact must include either:

  • linkedin_url (preferred — yields the highest match rate), or
  • first_name + last_name + (company_domain or company_name)
FieldTypeNotes
linkedin_urlstringLinkedIn profile URL
first_namestringRequired if no LinkedIn URL
last_namestringRequired if no LinkedIn URL
company_domainstringe.g., "acme.com" — preferred over name
company_namestringUsed as a fallback when no domain is available

A 422 response is returned if any contact fails validation, with the offending row index in the error detail.

Response

{
  "workflow_id": "public_bulk_4d6f2c3a-1b2e-4a5b-9c8d-3e2f1a0b9d8e",
  "status": "started",
  "message": "Bulk enrichment workflow started successfully",
  "task_ids": [
    "task_uuid_1",
    "task_uuid_2",
    "task_uuid_3"
  ],
  "total_contacts": 3
}
FieldDescription
workflow_idUse this to poll status or correlate webhook deliveries
statusInitial workflow state (started or running)
task_idsOne per contact — useful for fine-grained polling
total_contactsEcho of the input length

Limits and errors

ConditionStatusDetail
Empty contacts array400"At least one contact is required."
More than 250 contacts400"Bulk enrichment supports up to 250 contacts per request."
Contact missing required fields422Pydantic validation error
Insufficient credits402See Credits
Invalid API key401See Authentication
Rate limit exceeded429See Errors

Polling status

GET /api/v1/public/enrich/status

Poll either by workflow_id (for the entire batch) or by task_id (for a single contact). Provide exactly one — supplying both or neither returns 400.

# Workflow-level
curl "https://api.cleanlist.ai/api/v1/public/enrich/status?workflow_id=public_bulk_4d6f2c3a-..." \
  -H "Authorization: Bearer clapi_your_api_key"
 
# Task-level
curl "https://api.cleanlist.ai/api/v1/public/enrich/status?task_id=task_uuid_1" \
  -H "Authorization: Bearer clapi_your_api_key"

Query parameters

ParameterTypeDescription
workflow_idstringUUID-prefixed workflow id from a POST /enrich/bulk response
task_idstringTask UUID returned in the same response

You must supply exactly one. Both or neither → 400.

Response

When polling by workflow_id:

{
  "workflow": {
    "workflow_id": "public_bulk_4d6f2c3a-...",
    "status": "completed",
    "total_prospects": 3,
    "completed_prospects": 3,
    "failed_prospects": 0,
    "emails_found": 2,
    "phones_found": 0,
    "started_at": "2026-04-06T15:00:00Z",
    "completed_at": "2026-04-06T15:00:42Z"
  }
}

When polling by task_id:

{
  "task": {
    "task_id": "task_uuid_1",
    "workflow_id": "public_bulk_4d6f2c3a-...",
    "status": "completed",
    "result": {
      "primary_email": "john.doe@acme.com",
      "primary_email_status": "reliable",
      "personal_phone_numbers": []
    }
  }
}

The response uses response_model_exclude_none=True, so optional fields without values are omitted.

Workflow status values

StatusMeaning
pendingWorkflow accepted, not yet running
runningSome tasks still in flight
completedAll tasks finished successfully
completed_with_errorsWorkflow finished, but at least one task failed
failedWorkflow failed entirely

Polling vs webhooks

You don't have to choose — both can be used at once.

ApproachBest for
Polling GET /enrich/statusShort-lived scripts, dev/test, jobs you control end-to-end
Webhooks (webhook_url in bulk request)Production pipelines, serverless functions, anything where you don't want to keep a connection open

When you supply webhook_url, Cleanlist POSTs the complete result set to your endpoint when the workflow finishes. See Webhooks for the payload schema, retry behavior, and delivery logs.

DEFAULT lead list

Every Public API enrichment is automatically attached to a system-managed lead list named DEFAULT so the contacts remain visible in the Cleanlist portal. You don't need to create this list — it's created on first use and reused thereafter. See Lead Lists for the full explanation.

Related