New: watch the Cleanlist product tutorials. Watch now →
API Reference (v2)
SDKs
🔌

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: pip install cleanlist-aiCleanlist(access_token="clapi_…") → call typed methods like cl.workspace.whoami() and cl.enrichment.enrich_person(...). Sync and async clients included.

SDKs

Official, fully typed client libraries for the v2 API — each generated from the same OpenAPI schema the API serves, so they never drift. Available for Python, TypeScript/JavaScript, and Java:

LanguagePackageSource
Pythoncleanlist-ai (opens in a new tab) (sync + async)python-client (opens in a new tab)
TypeScript / JS@cleanlist-ai/sdk (opens in a new tab) (Node + browser)typescript-client (opens in a new tab)
Javaai.cleanlist:cleanlist-java (opens in a new tab) (OkHttp/Gson)java-client (opens in a new tab)

This page walks through the Python SDK end to end; TypeScript and Java mirror it with the same Cleanlist client and method names (see each repo's README).

Prefer raw HTTP? Every page in this section still shows cURL — the SDKs are optional sugar over the same https://api.cleanlist.ai/api/v2 surface.

Install

pip install cleanlist-ai

Python 3.8+. Pulls in both the sync (urllib3) and async (aiohttp) runtimes, so both clients work out of the box.

Authenticate

Create a key in the portal under Settings → API Keys (it starts with clapi_ and is shown once), then either pass it explicitly or export it and let the SDK pick it up:

export CLEANLIST_API_KEY="clapi_your_actual_key"
from cleanlist_ai import Cleanlist
 
cl = Cleanlist()  # reads CLEANLIST_API_KEY

Your first calls

whoami and credits/balance are free — a good end-to-end sanity check:

from cleanlist_ai import Cleanlist
 
with Cleanlist() as cl:
    me = cl.workspace.whoami()
    print(f"Org: {me.organization_name} | tier: {me.tier}")
    print(f"Credits: {cl.workspace.credits_balance().credits}")

The client exposes five resource groups, mirroring the API sections:

GroupAccessorCovers
Workspacecl.workspaceidentity, credits, API keys, usage
Lead Listscl.lead_listscreate/manage lists & leads
Enrichmentcl.enrichmentperson/company/bulk enrichment + status
Smart Agentscl.smart_agentsrun AI columns and read results
Exportcl.exportCSV (signed URL) and JSON export

Enrich a person and poll

Enrichment is asynchronous: the call dispatches a workflow and returns a workflow_id; poll enrichment_status until it settles.

import time
from cleanlist_ai import Cleanlist
from cleanlist_ai.models import CreateListRequest, EnrichPersonRequest
 
with Cleanlist() as cl:
    lst = cl.lead_lists.create_list(CreateListRequest(name="SDK demo"))
 
    job = cl.enrichment.enrich_person(
        EnrichPersonRequest(
            lead_list_id=lst.list_id,
            first_name="Ada",
            last_name="Lovelace",
            company_name="Analytical Engines",
        )
    )
    print("workflow:", job.workflow_id, "| reserved:", job.credits_reserved)
 
    while True:
        status = cl.enrichment.enrichment_status(job.workflow_id)
        if status.status in ("completed", "failed", "cancelled"):
            break
        time.sleep(3)
    print("charged:", status.credits_charged, "refunded:", status.credits_refunded)

Bulk enrichment (estimate → quote → run)

Bulk operations must be pre-priced with a quote. Call credits_estimate to get a signed, single-use quote_id, then pass it to enrich_list:

from cleanlist_ai.models import EstimateCostRequest, EnrichListRequest
 
quote = cl.workspace.credits_estimate(
    EstimateCostRequest(tool="enrich_list", list_id=lst.list_id, scope="full")
)
print(f"cost={quote.estimated_cost} sufficient={quote.sufficient}")
 
if quote.sufficient:
    run = cl.enrichment.enrich_list(
        EnrichListRequest(list_id=lst.list_id, scope="full", quote_id=quote.quote_id)
    )
    print("bulk workflow:", run.workflow_id)

Scopes: partial (email + LinkedIn + title + company, 1 credit) · phone_only (10 credits) · full (email and phone, 11 credits). Pricing is pay-for-results — the reservation is a cap and the unused portion is refunded.

Async

The async client lives under cleanlist_ai.aio with identical method names. Use it as an async context manager and await each call — asyncio.gather fires independent requests concurrently:

import asyncio
from cleanlist_ai.aio import Cleanlist
 
async def main():
    async with Cleanlist() as cl:
        me, balance = await asyncio.gather(
            cl.workspace.whoami(),
            cl.workspace.credits_balance(),
        )
        print(me.organization_name, balance.credits)
 
asyncio.run(main())

Error handling

Non-2xx responses raise a typed ApiException subclass carrying status, reason, and the parsed body:

from cleanlist_ai import ApiException
from cleanlist_ai.exceptions import NotFoundException, UnauthorizedException
 
try:
    cl.lead_lists.get_list("does-not-exist")
except NotFoundException:
    print("no such list")
except UnauthorizedException:
    print("bad or missing API key")
except ApiException as e:
    print(f"API error {e.status}: {e.body}")

See the Errors & Rate Limits page for status-code semantics.

Other languages

Official SDKs generated from the same v2 schema, with the same Cleanlist facade and method names:

Need another language? Generate a client from the OpenAPI spec with openapi-generator (opens in a new tab).

Next steps