TL;DR: Credits are deducted only when an enrichment succeeds. GET /public/credits/balance returns your organization's current balance. A 402 response on /public/enrich/bulk means you need to top up.
Credits
Cleanlist uses a credit-based pricing model. Credits live at the organization level — every user in your org draws from the same balance.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/public/credits/balance | Get current credit balance |
GET | /api/v1/public/auth/validate-key | Confirm a key is active (no credit cost) |
Get credit balance
GET /api/v1/public/credits/balance
curl https://api.cleanlist.ai/api/v1/public/credits/balance \
-H "Authorization: Bearer clapi_your_api_key"Response
{
"organization_id": "org_clerk_id",
"credits": 1234.5
}| Field | Type | Description |
|---|---|---|
organization_id | string | Your Clerk organization id |
credits | number | Current credit balance (can be fractional — smart columns charge 0.1–3.0 credits) |
The endpoint is rate-limited like the rest of the Public API (see Errors) but does not consume credits itself.
Validate an API key
GET /api/v1/public/auth/validate-key
A lightweight ping that confirms a key is valid without spending credits or hitting any other system. Use it from health checks, key-management scripts, or first-run setup wizards.
curl https://api.cleanlist.ai/api/v1/public/auth/validate-key \
-H "Authorization: Bearer clapi_your_api_key"Response
{
"valid": true,
"user_id": "user_clerk_id",
"organization_id": "org_clerk_id"
}A 401 means the key is missing, malformed, expired, or revoked.
Credit costs
Credits are deducted only when an enrichment succeeds. Failed lookups cost zero credits.
Enrichment
| Action | Credits |
|---|---|
Email found (partial) | 1 |
Phone found (phone_only) | 10 |
Email + phone found (full) | 11 |
| Prospecting (find new contacts at a company) | 1 per discovered contact |
| Enrichment that returns no data | 0 |
Smart Columns (portal feature)
If you layer Smart Columns onto a list, each column type has its own cost:
| Smart Column | Credits / row |
|---|---|
clean_first_name, format_phone | 0.1 |
email_validation | 0.2 |
enrich_company, linkedin_research, find_competitors, find_similar_companies, website_analysis, icp_fit_analysis, data_quality_*, contact_timezone | 0.5 |
custom_ai, custom_classification | 1.0 |
cold_intro_email | 3.0 |
Smart Columns are run from the portal but consume from the same organization credit pool.
Handling insufficient credits
When your organization runs out of credits, enrichment endpoints return 402 Payment Required:
{
"detail": "Insufficient credits"
}Recommended pattern
import requests
def enrich_with_topup_check(payload, api_key):
r = requests.post(
"https://api.cleanlist.ai/api/v1/public/enrich/bulk",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
if r.status_code == 402:
# Notify your billing channel — out of credits
notify_admin("Cleanlist credits exhausted; top up at portal.cleanlist.ai")
raise RuntimeError("Out of credits")
r.raise_for_status()
return r.json()Failed enrichments cost zero credits, so it is always safe to retry the same payload after a top-up.
Pre-flight check
For very large batches, check your balance first to fail fast:
balance = requests.get(
"https://api.cleanlist.ai/api/v1/public/credits/balance",
headers={"Authorization": f"Bearer {api_key}"},
).json()["credits"]
# Worst case: every contact yields a full enrichment (11 credits)
estimated_cost = len(contacts) * 11
if balance < estimated_cost:
raise RuntimeError(
f"Need ~{estimated_cost} credits, have {balance}. Top up first."
)This is a worst-case estimate — actual costs are usually lower because not every contact has both an email and a phone.
Topping up
Buy credits or change plans inside the portal (opens in a new tab) under Settings → Billing. New credits are available immediately after the Stripe payment completes.
Related
- Pricing — full pricing details and plan comparison
- Errors — all status codes
- Authentication — managing API keys