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-ai → Cleanlist(access_token="clapi_…") → call typed methods like cl.workspace.whoami() and cl.enrichment.enrich_person(...). Sync and async clients included.
Python SDK
The official Python SDK (cleanlist-ai) is the fastest way to use the v2 API from Python. It's a fully typed client — generated from the same OpenAPI schema the API serves, so it never drifts — with both a synchronous and an asynchronous client in one package.
- PyPI:
cleanlist-ai(opens in a new tab) - Source & full reference: github.com/Cleanlist-ai/cleanlist-python-client (opens in a new tab)
Prefer raw HTTP? Every page in this section still shows cURL — the SDK is optional sugar over the same https://api.cleanlist.ai/api/v2 surface.
Install
pip install cleanlist-aiRequires Python 3.8+. Installing 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_KEYYour 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:
| Group | Accessor | Covers |
|---|---|---|
| Workspace | cl.workspace | identity, credits, API keys, usage |
| Lead Lists | cl.lead_lists | create/manage lists & leads |
| Enrichment | cl.enrichment | person/company/bulk enrichment + status |
| Smart Agents | cl.smart_agents | run AI columns and read results |
| Export | cl.export | CSV (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:
- TypeScript / JavaScript —
@cleanlist-ai/sdk(opens in a new tab) (npm install @cleanlist-ai/sdk; Node + browser, ESM + CJS) - Java —
ai.cleanlist:cleanlist-java(opens in a new tab) (Maven / Gradle; OkHttp + Gson)
Need another language? Generate a client from the OpenAPI spec with openapi-generator (opens in a new tab).
Next steps
- Runnable example scripts (opens in a new tab) (person/bulk enrichment, smart agents, export, async).
- Full endpoint & model reference in the SDK README (opens in a new tab).
- Try requests live in the interactive API docs (opens in a new tab) (Swagger UI).
- The underlying OpenAPI spec if you'd rather generate a client in another language.