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

TL;DR: POST to /enrich for single contacts or /enrich/bulk for CSV uploads. Returns verified email, phone, LinkedIn data, and company info via the same waterfall enrichment system as the portal.

Enrichment API

Public enrichment is asynchronous. You submit contacts in bulk, receive identifiers, then poll or use webhooks for completion.

Start Bulk Enrichment

POST /api/v1/public/enrich/bulk

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",
    "contacts": [
      {
        "linkedin_url": "https://www.linkedin.com/in/levon-adamyan/",
        "first_name": "Levon",
        "last_name": "Adamyan",
        "company_domain": "cleanlist.ai"
      },
      {
        "first_name": "Levon",
        "last_name": "Adamyan",
        "company_name": "Cleanlist"
      }
    ]
  }'

Request Body

FieldTypeRequiredDescription
enrichment_typestringYesnone, partial, phone_only, or full
contactsarrayYesUp to 250 contacts per request
webhook_urlstringNoURL to receive a callback when enrichment completes

Contact Input Requirements

Each contact must include one of these two input patterns:

  • linkedin_url
  • first_name + last_name + (company_name or company_domain)

If contacts exceeds 250, the API returns 400 with a validation error.

LinkedIn-Only Payload Example

{
  "enrichment_type": "full",
  "contacts": [
    {
      "linkedin_url": "https://www.linkedin.com/in/levon-adamyan/"
    }
  ]
}

Enrichment Types

TypeBehaviorTypical use
noneNormalize and prepare records onlyStage/import contacts without appending new contact data
partialEmail enrichmentFind and verify business emails
phone_onlyPhone enrichmentFind direct phone numbers
fullEmail + phone enrichmentMax contact coverage

Response

{
  "workflow_id": "public_bulk_3d1f9a67-cc44-4a06-b8d4-7fe7a8f31a01",
  "status": "processing",
  "message": "Bulk enrichment started",
  "task_ids": [
    "6aab5e77-f5c0-4233-8f8e-a0e1a9baf18c",
    "d2c1e5f2-2d83-47f2-a620-4d27906b9f65"
  ],
  "total_contacts": 2
}
⚠️

The API validates credit availability before processing. If insufficient, the request can return 402.

Poll Enrichment Status

GET /api/v1/public/enrich/status

Provide exactly one query parameter:

  • workflow_id for aggregate workflow progress
  • task_id for single task status

Workflow Status Example

curl "https://api.cleanlist.ai/api/v1/public/enrich/status?workflow_id=public_bulk_3d1f9a67-cc44-4a06-b8d4-7fe7a8f31a01" \
  -H "Authorization: Bearer clapi_your_api_key"

Task Status Example

curl "https://api.cleanlist.ai/api/v1/public/enrich/status?task_id=6aab5e77-f5c0-4233-8f8e-a0e1a9baf18c" \
  -H "Authorization: Bearer clapi_your_api_key"

Response (task):

{
  "task": {
    "task_id": "6aab5e77-f5c0-4233-8f8e-a0e1a9baf18c",
    "status": "completed",
    "message": "Enrichment completed"
  }
}

Webhooks

To receive webhook callbacks, pass webhook_url in your bulk enrichment request. The URL will receive a POST when the workflow completes.

List Delivery Attempts

GET /api/v1/public/webhooks/deliveries?workflow_id=<workflow_id>

curl "https://api.cleanlist.ai/api/v1/public/webhooks/deliveries?workflow_id=public_bulk_3d1f9a67-cc44-4a06-b8d4-7fe7a8f31a01" \
  -H "Authorization: Bearer clapi_your_api_key"

Polling Best Practice

Use backoff when polling status:

import time
import requests
 
 
def poll_workflow(workflow_id, api_key, max_wait=300):
    url = "https://api.cleanlist.ai/api/v1/public/enrich/status"
    headers = {"Authorization": f"Bearer {api_key}"}
 
    delay = 1
    elapsed = 0
 
    while elapsed < max_wait:
        response = requests.get(url, headers=headers, params={"workflow_id": workflow_id})
        data = response.json()
 
        workflow = data.get("workflow", {})
        if workflow.get("status") in ("completed", "failed"):
            return workflow
 
        time.sleep(delay)
        elapsed += delay
        delay = min(delay * 2, 10)
 
    raise TimeoutError(f"Workflow {workflow_id} did not complete within {max_wait}s")

Rate Limits

Public endpoints documented on this page are limited to 60 requests per minute per organization in a fixed 60-second window.

If you exceed the limit, the API returns 429 Too Many Requests with a Retry-After header.

Example 429 response:

HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json
 
{
  "detail": "Rate limit exceeded. Public API is limited to 60 requests per minute."
}