TL;DR: Generate a key → check credits → submit a bulk enrichment with webhook_url → handle the webhook → audit deliveries. This guide walks you through it end-to-end with copy-paste examples.
API Quickstart
Goal: get from "no API key" to "enrichment results landing in your app via webhook" in under 10 minutes.
Prerequisites
- A Cleanlist account at portal.cleanlist.ai (opens in a new tab)
- A plan that includes API access
- A publicly reachable HTTPS endpoint that can receive a POST (use webhook.site (opens in a new tab) for testing if you don't have one yet)
Step 1 — Generate an API key
- Log in to portal.cleanlist.ai (opens in a new tab)
- Navigate to Settings → API Keys
- Click Generate Key
- Give it a name (e.g.,
quickstart-test) - Copy the full key — it starts with
clapi_and is shown once
Store it in an environment variable:
export CLEANLIST_API_KEY="clapi_your_actual_key"Step 2 — Confirm the key works
A fast, zero-credit sanity check:
curl https://api.cleanlist.ai/api/v1/public/auth/validate-key \
-H "Authorization: Bearer $CLEANLIST_API_KEY"You should see {"valid": true, "user_id": "...", "organization_id": "..."}.
Step 3 — Check your credit balance
curl https://api.cleanlist.ai/api/v1/public/credits/balance \
-H "Authorization: Bearer $CLEANLIST_API_KEY"You'll need at least a few credits to run the example below.
Step 4 — Submit a bulk enrichment
Pass webhook_url to receive results when the workflow finishes:
curl -X POST https://api.cleanlist.ai/api/v1/public/enrich/bulk \
-H "Authorization: Bearer $CLEANLIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enrichment_type": "partial",
"webhook_url": "https://webhook.site/your-unique-url",
"contacts": [
{
"first_name": "John",
"last_name": "Doe",
"company_domain": "acme.com"
},
{
"linkedin_url": "https://www.linkedin.com/in/janedoe"
}
]
}'The response is immediate. The workflow runs in the background.
Step 5a — Receive the result via webhook
When the workflow finishes, Cleanlist POSTs to your webhook URL with a payload like:
{
"event": "enrichment.completed",
"workflow_id": "public_bulk_4d6f2c3a-...",
"lead_list_id": "550e8400-...",
"enrichment_type": "partial",
"status": "completed",
"summary": {
"total": 2,
"successful": 2,
"failed": 0,
"emails_found": 2,
"phones_found": 0
},
"results": [
{
"task_id": "task_1",
"primary_email": "john.doe@acme.com",
"primary_email_status": "reliable",
"prospect": { "first_name": "John", "last_name": "Doe" },
"company": { "name": "Acme Corp", "domain": "acme.com" }
}
],
"completed_at": "2026-04-06T15:00:42Z"
}A minimal Express receiver:
import express from "express";
const app = express();
app.use(express.json({ limit: "10mb" }));
app.post("/webhooks/cleanlist", (req, res) => {
console.log("Received:", req.body.workflow_id, req.body.status);
console.log(`${req.body.summary.successful} of ${req.body.summary.total} enriched`);
// ... persist results, then 200 to ack
res.status(200).end();
});
app.listen(3000);See the Receiving Webhooks guide for production patterns.
Step 5b — Or poll for status
If you'd rather poll than use webhooks (or both):
import time
def wait_for_workflow(workflow_id, poll_seconds=5):
while True:
r = requests.get(
"https://api.cleanlist.ai/api/v1/public/enrich/status",
headers=HEADERS,
params={"workflow_id": workflow_id},
)
r.raise_for_status()
wf = r.json().get("workflow", {})
status = wf.get("status")
print(f" status={status}")
if status in {"completed", "completed_with_errors", "failed"}:
return wf
time.sleep(poll_seconds)
final = wait_for_workflow(workflow_id)
print(f"Done: {final}")Step 6 — Audit webhook deliveries
If your webhook endpoint is flaky and you want to confirm delivery actually happened, query the delivery log:
deliveries = requests.get(
"https://api.cleanlist.ai/api/v1/public/webhooks/deliveries",
headers=HEADERS,
params={"workflow_id": workflow_id},
).json()
for attempt in deliveries:
print(
f"Attempt #{attempt['attempt_number']}: "
f"{attempt['status']} ({attempt['response_status_code']}) "
f"in {attempt['duration_ms']}ms"
)You'll see one row per attempt, including any retries.
What you've built
You now have:
- ✓ A working
clapi_API key - ✓ A live credit-balance check
- ✓ A bulk enrichment workflow running asynchronously
- ✓ A webhook-based result handler
- ✓ Visibility into delivery attempts
From here you can:
- Wire it into your real ETL or CRM pipeline
- Layer on Smart Columns in the portal for richer enrichment (see Smart Columns)
- Set up multiple keys for dev / staging / production (see Authentication)
- Read the Errors reference for production-ready error handling
Related
- API Reference — full endpoint reference
- Receiving Webhooks — production webhook patterns
- Errors — status codes and retry strategies
- Troubleshooting — common issues