Skip to Content
New: meet Clu, and send email, LinkedIn and call steps with Sequences. Watch the videos →
🔌

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: POST /export/csv/signed-url builds a CSV of a list and returns a download link valid for 24 hours; a list that takes longer than 20 seconds answers 202 with a job_id to call back with. GET /export/json returns leads inline, up to 500 per call, with cursor paging. Both need export:read and are free.

Export

Two ways to get a lead list out of CluGTM through API v2. To export from the app instead, see Exporting data.

EndpointMethodScopeCost
CSV (signed URL)POST /export/csv/signed-urlexport:readFree
JSONGET /export/jsonexport:readFree

Columns

Both endpoints use the same columns. Without columns, you get the default set, in this order:

ColumnContents
lead_idLead id
first_name, last_nameName
emailPrimary email (empty until enriched)
titleJob title
companyCompany name
domainCompany domain
linkedin_urlLinkedIn profile URL
phoneFirst phone number (empty until phone-enriched)
locationCity and country

Pass columns to pick a subset and set the order. Stick to these names; other names may come back empty.

CSV export

POST /export/csv/signed-url

Scope: export:read · Cost: free

Starts an export of the whole list (import rows not yet enriched come first) and waits up to 20 seconds for it. If the file is ready, the response has status: "completed" and a signed link. A bigger list answers HTTP 202 with status: "running", a job_id and download_url: null: call again with the same list_id and the job_id to get the link. A call with job_id starts no new export, and it waits up to 20 seconds again.

Request body

FieldTypeNotes
list_idstringRequired. A list you can read
columnsstring[]Optional ordered subset of the columns above
include_smart_agentsbooleanDefault true: adds one column per skill or smart-agent result saved on the list (headed by its display name) after your columns. false exports only the base columns
job_idstringThe job_id of an export that answered status: "running". Send it with the same list_id to get the link
curl -X POST https://api.cleanlist.ai/api/v2/export/csv/signed-url \ -H "Authorization: Bearer clapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{"list_id": "LIST_ID", "columns": ["first_name", "last_name", "email", "company"]}'

Response

{ "list_id": "LIST_ID", "job_id": "JOB_ID", "status": "completed", "download_url": "https://...", "expires_at": "2026-10-08T21:20:00+00:00", "row_count": 1284, "file_size_bytes": 402118, "skipped_count": 0 }

A list that isn’t ready within 20 seconds answers 202:

{ "list_id": "LIST_ID", "job_id": "JOB_ID", "status": "running", "download_url": null, "expires_at": null, "row_count": 48210, "file_size_bytes": null, "skipped_count": 0 }
curl -X POST https://api.cleanlist.ai/api/v2/export/csv/signed-url \ -H "Authorization: Bearer clapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{"list_id": "LIST_ID", "job_id": "JOB_ID"}'
FieldMeaning
statuscompleted with a download_url, or running (HTTP 202): call again with job_id
job_idThe export job. Send it back to poll a running export
download_urlSigned link to the CSV file (null while running). Anyone with the link can download it until it expires, so treat it like a secret
expires_atWhen the link stops working, 24 hours after it was made
row_countData rows in the file (not counting the header); while running, the rows being exported
file_size_bytesFile size
skipped_countRows that changed while the list was exporting (deleted, or merged into another row) and aren’t in the file. row_count + skipped_count is the list’s row count when the export started

To stop spreadsheet apps from running formulas, any cell that starts with =, +, - or @ is written with a leading apostrophe (').

🧑‍💻

With an SDK: export.export_csv(...) and export.export_json(...) (exportCsv and exportJson in TS and Java). See the SDKs.

JSON export

GET /export/json

Scope: export:read · Cost: free

Returns a page of the list’s leads inline, using the same columns.

Query paramDefaultNotes
list_idnoneRequired
limit1001 to 500 rows per call
cursornoneThe cursor from the previous response
columnsdefault setRepeat the parameter for each column, for example columns=email&columns=company
curl "https://api.cleanlist.ai/api/v2/export/json?list_id=LIST_ID&limit=500" \ -H "Authorization: Bearer clapi_your_api_key"
{ "list_id": "LIST_ID", "leads": [ { "lead_id": "lead_5f9d2b", "first_name": "Ada", "last_name": "Lovelace", "email": "ada@analytical.io", "company": "Analytical", "domain": "analytical.io" } ], "total": 1284, "cursor": "500", "skipped": 0, "timestamp_ms": 1751404800000 }

Pass cursor back to get the next page; it’s null on the last page. total is the number of rows in the whole list, import rows not yet enriched included. skipped counts rows of this page’s place in the order that left the list (deleted or moved) after paging started, so the page holds fewer rows than it covers. A malformed cursor returns 400 invalid_cursor instead of starting over, and a cursor unused for 15 minutes returns 409 cursor_expired: start again without a cursor.

Errors

CodeHTTPCause
insufficient_scope403The credential lacks export:read
list_not_found404Unknown list, or a private list you can’t access
invalid_cursor400The JSON cursor isn’t one the API returned
cursor_expired409The JSON cursor’s ordering expired (15 minutes unused). Start again without a cursor
not_found404No CSV export has that job_id for this list. Start a new export without job_id
internal_error502The export couldn’t be started, read or built; retry