New: meet Clu, and send email, LinkedIn and call steps with Sequences. Watch the videos →
API Reference (v2)
Credits & Quotes
🔌

Audience: developers. API keys need a Pro or Enterprise plan (legacy Pro and Scale plans included) or an AppSumo Tier 5, 6 or 7 license, and they never work during a trial. 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, single use). Running short returns 400 insufficient_credits with the exact shortfall. GET /usage reports spend over time. Reads, saves and status polls are free.

Credits

Cleanlist API calls that search, read, save, estimate or poll are free. A work email costs 1 credit, a direct dial 10 and both on one row 11, and a lookup that finds nothing costs 0.

Cleanlist bills enrichment, smart agents and sync in credits shared by the whole organization. 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 and the quote model

POST /credits/estimate

Scope: credits:read · Cost: free

Prices a paid operation. Returns a quote_id bound (with HMAC-SHA256) to the exact request shape: tool, list or cohort, scope, row count, destination and your organization. The quote lasts 5 minutes and can be used once. At execution the server recomputes the real cost and rejects the call with spend_cap_exceeded if it would exceed the quote.

When you pass a list_id (or a cohort task_id), the row count is always derived on the server; any row_count you send is ignored, so you can't under-quote a large list.

POST /credits/estimate is live but not in the served OpenAPI spec. Call it over HTTP, or use workspace.credits_estimate(...) in the SDKs.

Request body

FieldTypeUsed for
toolstringRequired. The operation to price (see the cost table). Unknown tools return 400
list_idstringList-backed tools (enrich_list, run_smart_agent, sync_to_crm, sync_to_sequencer); row count is read from the list
task_idstringCohort tools (add_person_enrichments, add_company_enrichments); row count is read from the cohort
entity_idsstring[]Optional subset of a cohort to price
data_pointsstring[]Cohort person enrichment: Email, Phone, LinkedIn
row_countintegerTools that aren't list- or cohort-backed (for example bulk_import_csv)
scopestringenrich_list: partial, full or phone-only
enrichment_typestringenrich_person and bulk_import_csv: partial, phone_only or full
agent_typestringrun_smart_agent: custom_ai, cold_intro_email, title_normalizer, company_intel or pain_point_hypothesis
column_name, prompt, template_id, lead_scope, max_rows, overwritevariousrun_smart_agent: must match the run request exactly
providerstringSync: hubspot or salesforce (CRM), lemlist (sequencer)
sub_actionstringsync_to_crm: contacts for HubSpot; leads (default) or contacts for Salesforce
campaign_idstringsync_to_sequencer: the Lemlist campaign
field_mappingobjectSync: must match the sync request exactly
include_phonebooleanLegacy person pricing flag; prefer enrichment_type

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 at most (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"}'
🧑‍💻

With an SDK: workspace.credits_estimate(...) (creditsEstimate in TS and Java) returns the signed quote_id. See the SDKs.

Quote lifecycle

  1. Estimate: mint a quote_id for the request shape.
  2. Execute: pass the quote_id to the paid endpoint. The server checks the signature and recomputes the cost.
  3. Single use: a quote can't be redeemed twice (quote_already_redeemed: 409 on smart agents and sync, 400 on enrichment).
  4. Expiry: after 5 minutes the quote is rejected (quote_expired); estimate again.
  5. Shape change: a quote for a different list, scope, destination or mapping is rejected (quote_mismatch). If the cost grew past the quote, you get spend_cap_exceeded. A row count may drift by up to 10% before the quote is rejected.
  6. Can't confirm: if the server can't confirm the quote is unused, it returns 503 quote_verification_unavailable (retryable).

Insufficient credits

Paid operations check your balance before they start. If you can't cover the cost, you get HTTP 400 with code insufficient_credits:

{
  "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.

The fix text is returned verbatim by the API and mentions app.cleanlist.ai/billing. That page does not exist: buy credits or change plan on the Plans page at app.cleanlist.ai/plans (opens in a new tab) (Settings → Plans & billing). Credit top-ups aren't available during a trial.

Usage reports

GET /usage

Scope: admin:api_keys · Cost: free

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

Query paramDefaultRange or values
days71 to 365
group_bytooltool, key, day or 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": "/people/find", "calls": 980, "credits_spent": 0, "errors": 1 }
  ]
}

With group_by=tool, rows are bucketed by the endpoint template (the path without the /api/v2 prefix, for example /enrichment/person or /lead-lists/{list_id}/leads). credits_spent and total_credits_spent are whole numbers: each request's cost is rounded up before it's logged. The figure is the cost reserved at dispatch, not the settled charge after pay-for-results refunds. Free endpoints (search, saving leads) report 0.

Credit cost table

Costs are computed per request and the total is rounded up to a whole credit. Reads are always free. Use the tool name with POST /credits/estimate.

OperationTool name (for estimate)Cost
People searchsearch_peopleFree
Company searchsearch_companiesFree
Similar companiesfind_similar_companiesNo credits deducted (the estimate returns 1; no quote needed)
Save leads to a listadd_leads_to_listFree
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
Cohort person enrichmentadd_person_enrichmentsPer person: Email 1 + Phone 10 (LinkedIn is free; a LinkedIn-only cohort is rejected)
Cohort company enrichmentadd_company_enrichmentsceil(n / 10), about 1 credit per 10 companies
Bulk list enrichment, partialenrich_list (partial)1 per lead
Bulk list enrichment, fullenrich_list (full)11 per lead
Bulk list enrichment, phone onlyenrich_list (phone-only)10 per lead
CSV importbulk_import_csvImport free; enrichment per lead (partial 1, phone_only 10, full 11)
Smart agent, cold intro emailrun_smart_agent (cold_intro_email)3 per lead
Smart agent, presetsrun_smart_agent (title_normalizer, company_intel, pain_point_hypothesis)1 per lead
Smart agent, custom AIrun_smart_agent (custom_ai)Priced from its prompt. Read estimated_cost from the quote
Sync to CRMsync_to_crm0.2 per lead
Sync to sequencersync_to_sequencer0.2 per lead
Find people at list companies(no quote)Free; the job needs a balance of at least companies × limit_per_company to start
whoami, balance, estimate, list reads, status polls, exportsread endpointsFree

Enrichment is pay-for-results: data that isn't found isn't charged. Async enrichments reserve the estimated credits up front and refund the unused part when the workflow settles. Sync charges every lead in the list once the push completes, including leads the destination skipped or rejected. See Enrichment and CRM & Sequencer Sync.

Learn more