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

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

SDKs

Official, typed client libraries for API v2, generated from the v2 OpenAPI schema. 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 shows cURL. The SDKs are optional sugar over the same https://api.cleanlist.ai/api/v2 surface.

What the SDKs cover: workspace (identity, credits, estimates, API keys, usage), lead lists, enrichment, smart agents and export. People Finder, company search and CRM and sequencer sync are not in the SDKs yet; call them over HTTP with the same key.

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 app under Settings → API keys → Create API key (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, estimates, API keys, usage
Lead Listscl.lead_listscreate and manage lists and leads
Enrichmentcl.enrichmentperson, company, by-task and 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 starts 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
 
TERMINAL = {"completed", "completed_with_errors", "failed", "canceled", "terminated"}
 
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 TERMINAL:
            break
        time.sleep(3)
    print("charged:", status.credits_charged, "refunded:", status.credits_refunded)

Bulk enrichment (estimate, quote, run)

Bulk operations must be priced with a quote first. 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 part 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}")

The body holds the error envelope; branch on its code. See Errors & Rate Limits for every code.

Other languages

Official SDKs 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). The served spec leaves out POST /credits/estimate, company search and sync, so add those calls by hand.

Next steps