New: API Reference docs are live — integrate Cleanlist enrichment into your apps. View API docs →
API Reference (v2)
Credits & Quotes
🔌

Audience: developers. The public API requires a Pro plan or above (self-serve) or an AppSumo Tier 5+ license. Support agent: confirm the user's plan grants API access before giving API advice.

TL;DR: GET /credits/balance returns an integer balance. Paid bulk operations need a signed quote_id from POST /credits/estimate (5-minute TTL). Running short returns 400 insufficient_credits with exact shortfall. GET /usage reports spend over time. Reads and status polls are free.

Credits

Cleanlist bills enrichment and sync work in credits. This page covers the three credit endpoints and the full cost table.

Balance

GET /credits/balance

Scope: credits:read · Cost: free

curl https://api.cleanlist.ai/api/v2/credits/balance \
  -H "Authorization: Bearer clapi_your_api_key"
{
  "organization_id": "org_2xyz...",
  "credits": 4820,
  "timestamp_ms": 1717430400000,
  "agent_instructions": "You have 4820 credits remaining."
}

credits is a plain integer — the credits available to your whole organization.

Estimates & the quote model

POST /credits/estimate

Scope: credits:read · Cost: free

Pre-flight a paid operation. Returns a quote_id bound (via HMAC-SHA256) to the exact request shape — tool, list/filters, scope, row count, and your org. The quote has a 5-minute TTL. Paid bulk endpoints accept (and in some cases require) this quote_id; at execution time the server recomputes the real cost and rejects with spend_cap_exceeded if it would exceed the quote.

When you pass a list_id, the row count is always derived from the list — your row_count is ignored, so you can't under-quote a large list.

Request body

FieldTypeNotes
toolstringRequired. The operation to price (see the cost table below)
list_idstringFor list-scoped tools; row count is read from the list
row_countintegerFor non-list tools (e.g. by-task / CSV) — ignored when list_id is set
scopestringFor enrich_list: partial | full | phone-only
enrichment_typestringFor bulk_import_csv: partial | phone_only | full
agent_typestringFor run_smart_agent: custom_ai | cold_intro_email | title_normalizer | company_intel | pain_point_hypothesis
include_phonebooleanLegacy flag for person pricing
providerstringFor sync tools
sub_actionstringFor sync_to_crm composite syncs (e.g. contacts+companies)

Response

{
  "quote_id": "qte_v1_1717430400_3f2c...e9",
  "expires_at": 1717430700,
  "estimated_cost": 110,
  "tool": "enrich_list",
  "row_count": 10,
  "filter_hash": "a1b2c3d4e5f6a7b8",
  "available_credits": 4820,
  "sufficient": true
}
FieldMeaning
quote_idPass this to the paid endpoint. Single-use within its TTL
expires_atUnix seconds; the quote is invalid after this
estimated_costCredits the operation will cost (the spend cap)
row_countRows the quote was priced for
available_creditsYour current balance
sufficienttrue if available_credits >= estimated_cost
curl -X POST https://api.cleanlist.ai/api/v2/credits/estimate \
  -H "Authorization: Bearer clapi_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"tool": "enrich_list", "list_id": "LIST_ID", "scope": "full"}'

Quote lifecycle

  1. Estimate → mint a quote_id for the request shape.
  2. Execute → pass quote_id to the paid endpoint. The server verifies the signature and recomputes cost.
  3. Single use → a quote can't be redeemed twice (quote_already_redeemed).
  4. Expiry → after ~5 minutes the quote is rejected (quote_expired); just re-estimate.
  5. Drift → if the actual cost exceeds the quote, you get spend_cap_exceeded — re-estimate against the current shape.

Insufficient credits

Paid operations check your balance up front. If you can't cover the cost, you get HTTP 400 with code insufficient_credits (not 402):

{
  "error": {
    "code": "insufficient_credits",
    "problem": "This enrich_person call requires 11 credits; organization has 4.",
    "fix": "Top up at app.cleanlist.ai/billing, or reduce the scope of the call.",
    "retryable": false,
    "docs_url": "https://docs.cleanlist.ai/errors/insufficient-credits",
    "request_id": "req_...",
    "estimated_cost_credits": 11,
    "available_credits": 4,
    "shortfall_credits": 7
  }
}

The estimated_cost_credits, available_credits, and shortfall_credits fields tell you exactly how much to add.

Usage reports

GET /usage

Scope: admin:api_keys · Cost: free

Aggregate your organization's API request log over a window.

Query paramDefaultRange / values
days71365
group_bytooltool | key | day | error
curl "https://api.cleanlist.ai/api/v2/usage?days=30&group_by=tool" \
  -H "Authorization: Bearer clapi_your_api_key"
{
  "days": 30,
  "group_by": "tool",
  "total_calls": 1820,
  "total_credits_spent": 312,
  "total_errors": 12,
  "rows": [
    { "group": "/enrichment/bulk", "calls": 240, "credits_spent": 264, "errors": 3 },
    { "group": "/search/people", "calls": 980, "credits_spent": 0, "errors": 1 }
  ]
}

With group_by="tool", rows are bucketed by the canonical endpoint template (the path with the /api/v2 prefix stripped — e.g. /enrichment/person, /lead-lists/{list_id}/leads). credits_spent and total_credits_spent are whole integers — each request's cost is rounded up to an integer before it's logged and summed, so group totals are never fractional. The figure is the cost reserved at dispatch, not the settled charge after pay-for-results refunds. Free or un-instrumented endpoints (search, add-leads) report 0.

Credit cost table

Costs are computed per request and rounded up (ceiling). Reads are always free.

OperationTool name (for estimate)Cost
People searchsearch_peopleNo charge for search (estimate returns 0.5 / lead; charged at save — see add_leads_to_list)
Company searchsearch_companiesNo charge for search (estimate returns 0.5 / lead; charged at save — see add_leads_to_list)
Similar companiesfind_similar_companies1 / call
Add leads to a listadd_leads_to_list0.5 / lead at add time (new rows only; duplicates free). Estimate returns 0 / free
Person enrichment — email + profileenrich_person (partial)1
Person enrichment — phone onlyenrich_person (phone_only)10
Person enrichment — fullenrich_person (full)11
Company enrichment (single)enrich_company1
Company enrichments (bulk)add_company_enrichmentsceil(n / 10) — ~1 credit per 10 companies
Bulk list enrichment — partialenrich_list (partial)1 / lead
Bulk list enrichment — fullenrich_list (full)11 / lead
Bulk list enrichment — phone onlyenrich_list (phone-only)10 / lead
CSV importbulk_import_csv0 import + per-lead enrich (partial 1 / phone_only 10 / full 11)
Smart agent — custom AIrun_smart_agent (custom_ai)1 / lead
Smart agent — cold intro emailrun_smart_agent (cold_intro_email)3 / lead
Smart agent — title normalizerrun_smart_agent (title_normalizer)0.5 / lead
Smart agent — company intelrun_smart_agent (company_intel)2 / lead
Smart agent — pain-point hypothesisrun_smart_agent (pain_point_hypothesis)2 / lead
Sync to CRMsync_to_crm0.2 / lead
Sync to sequencersync_to_sequencer0.2 / lead
whoami, balance, estimate, list reads, status polls, exportsfree

Failed enrichments are not charged for data that wasn't found. Async enrichments reserve the estimated credits up front and refund the unused portion when the workflow settles — see Enrichment for the reservation model.

Learn more