Official Java client for the Cleanlist API (v2) — B2B lead discovery, waterfall enrichment, lead lists, smart agents, and export.
Generated from the public v2 OpenAPI schema (OkHttp + Gson), with a small Cleanlist
convenience facade so you can get productive in a few lines.
import ai.cleanlist.client.Cleanlist;
import ai.cleanlist.client.model.CreateListRequest;
Cleanlist cl = new Cleanlist("clapi_live_...");
System.out.println(cl.workspace.whoami().getOrganizationName());
var list = cl.leadLists.createList(new CreateListRequest().name("Q3 outbound"));
System.out.println("created list: " + list.getListId());- ✅ Fully typed request & response models (fluent builders + getters)
- ✅ OkHttp transport, Gson serialization
- ✅ Bearer-token auth, production defaults
- ✅ Java 8+ · Maven & Gradle
- ✅ Generated from the same schema the API serves, so it never drifts
- Installation
- Authentication
- Quickstart
- Configuration
- Core concepts
- Endpoint reference
- Error handling
- Pagination
- Using the generated API classes directly
- Regenerating from the schema
- Support
Maven
<dependency>
<groupId>ai.cleanlist</groupId>
<artifactId>cleanlist-java</artifactId>
<version>2.0.0</version>
</dependency>Gradle
implementation 'ai.cleanlist:cleanlist-java:2.0.0'Requires Java 8+.
Every request is authenticated with a Cleanlist API key, sent as an
Authorization: Bearer <key> header. Create one in the portal under
Settings → API Keys (keys start with
clapi_).
// Explicit
Cleanlist cl = new Cleanlist("clapi_live_...");
// Or read the CLEANLIST_API_KEY environment variable
Cleanlist cl2 = Cleanlist.fromEnv();Keep keys secret. Never commit them. Prefer environment variables or a secrets manager over hard-coding.
import ai.cleanlist.client.Cleanlist;
import ai.cleanlist.client.ApiException;
import ai.cleanlist.client.model.*;
public class Quickstart {
public static void main(String[] args) throws ApiException {
Cleanlist cl = Cleanlist.fromEnv(); // CLEANLIST_API_KEY
// 1. Who am I? (identity, tier, scopes)
WhoamiResponse me = cl.workspace.whoami();
System.out.printf("Org: %s | tier: %s%n", me.getOrganizationName(), me.getTier());
// 2. Credit balance
System.out.println("credits: " + cl.workspace.creditsBalance().getCredits());
// 3. Create a lead list
ListDetailResponse list = cl.leadLists.createList(new CreateListRequest().name("Demo — API"));
// 4. Enrich a person into it (async workflow — returns a handle)
EnrichPersonResponse job = cl.enrichment.enrichPerson(new EnrichPersonRequest()
.leadListId(list.getListId())
.firstName("Ada").lastName("Lovelace").companyName("Analytical Engines"));
System.out.printf("workflow: %s | reserved: %d%n", job.getWorkflowId(), job.getCreditsReserved());
}
}Every API method throws the checked
ai.cleanlist.client.ApiExceptionon a non-2xx response — declarethrows ApiExceptionor wrap in try/catch.
Pass a base path for local development, and reach the underlying ApiClient for
timeouts / interceptors:
import ai.cleanlist.client.Cleanlist;
Cleanlist cl = new Cleanlist("clapi_live_...", "http://localhost:8000");
// Advanced: tweak the underlying OkHttp-backed client
cl.getApiClient().setConnectTimeout(10_000);
cl.getApiClient().setReadTimeout(30_000);Search and list management are free; enrichment and smart-agent runs cost credits.
Bulk/paid operations (enrichList, runSmartAgent, and CSV import with enrichment)
require a signed quote from creditsEstimate first. The quote pins the price and is
single-use:
EstimateCostResponse quote = cl.workspace.creditsEstimate(
new EstimateCostRequest().tool("enrich_list").listId(list.getListId()).scope("full"));
System.out.printf("cost=%d sufficient=%b%n", quote.getEstimatedCost(), quote.getSufficient());
if (Boolean.TRUE.equals(quote.getSufficient())) {
EnrichListResponse run = cl.enrichment.enrichList(new EnrichListRequest()
.listId(list.getListId())
.scope(EnrichListRequest.ScopeEnum.FULL)
.quoteId(quote.getQuoteId()));
System.out.println("bulk workflow: " + run.getWorkflowId());
}Enrichment 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.
enrichPerson, enrichCompany, enrichByTask, and enrichList dispatch a workflow
and return a workflowId. Poll enrichmentStatus until it settles:
import java.util.Set;
Set<String> terminal = Set.of("completed", "failed", "cancelled");
WorkflowStatusResponse status;
do {
Thread.sleep(3000);
status = cl.enrichment.enrichmentStatus(job.getWorkflowId());
System.out.printf(" %s %d/%d%n", status.getStatus(), status.getProcessed(), status.getTotal());
} while (!terminal.contains(status.getStatus()));
System.out.printf("charged=%d refunded=%d%n", status.getCreditsCharged(), status.getCreditsRefunded());The v2 surface is 24 operations across five resource groups, exposed on the Cleanlist
facade as cl.workspace, cl.leadLists, cl.enrichment, cl.smartAgents, and
cl.export. Field-by-field model docs live in docs/.
| Method | HTTP | Description |
|---|---|---|
whoami() |
GET /api/v2/whoami |
Identity, org, tier, scopes & features. |
creditsBalance() |
GET /api/v2/credits/balance |
Spendable credit balance. |
creditsEstimate(EstimateCostRequest) |
POST /api/v2/credits/estimate |
Price an op, get a signed quote. |
listApiKeys() |
GET /api/v2/api-keys |
List the org's API keys. |
usageReport(Integer days, String groupBy) |
GET /api/v2/usage |
Credit-usage report. |
| Method | HTTP | Description |
|---|---|---|
createList(CreateListRequest) |
POST /api/v2/lead-lists |
Create a list (idempotent on name). |
listLists(String folderId, Integer limit, String cursor) |
GET /api/v2/lead-lists |
List your lists (paginated). |
getList(String listId) |
GET /api/v2/lead-lists/{list_id} |
Fetch one list. |
updateList(String listId, PublicLeadListUpdate) |
PATCH …/{list_id} |
Rename / move / edit. |
deleteList(String listId) |
DELETE …/{list_id} |
Delete a list. |
listLeadsInList(String listId, Integer limit, String cursor) |
GET …/{list_id}/leads |
Page through leads. |
addLeadsToList(String listId, Body) |
POST …/{list_id}/leads |
Add leads by id or cohort. |
removeLeadsFromList(String listId, RemoveLeadsRequest) |
DELETE …/{list_id}/leads |
Remove up to 100. |
csvImport(String listId, CsvImportRequest) |
POST …/{list_id}/csv-import |
Import from base64 CSV. |
import java.util.Arrays;
ListDetailResponse list = cl.leadLists.createList(new CreateListRequest().name("Prospects — West"));
LeadsPageResponse page = cl.leadLists.listLeadsInList(list.getListId(), 100, null);
// `Body` is a one-of: wrap AddByLeadIds OR AddByCohort (a search task_id)
cl.leadLists.addLeadsToList(list.getListId(),
new Body(new AddByLeadIds().leadIds(Arrays.asList("lead_a", "lead_b"))));
cl.leadLists.removeLeadsFromList(list.getListId(),
new RemoveLeadsRequest().leadIds(Arrays.asList("lead_a")));| Method | HTTP | Description |
|---|---|---|
enrichPerson(EnrichPersonRequest) |
POST /api/v2/enrichment/person |
Enrich one contact into a list. |
enrichCompany(EnrichCompanyRequest) |
POST /api/v2/enrichment/company |
Enrich a company. |
enrichByTask(EnrichByTaskRequest) |
POST /api/v2/enrichment/by-task |
Enrich entities from a prior task. |
enrichList(EnrichListRequest) |
POST /api/v2/enrichment/bulk |
Bulk-enrich a list (needs quoteId). |
enrichmentStatus(String workflowId) |
GET /api/v2/enrichment/status/{workflow_id} |
Poll a workflow. |
EnrichPersonResponse job = cl.enrichment.enrichPerson(new EnrichPersonRequest()
.leadListId(list.getListId()).linkedinUrl("https://linkedin.com/in/ada"));
EnrichCompanyResponse company = cl.enrichment.enrichCompany(new EnrichCompanyRequest().domain("stripe.com"));| Method | HTTP | Description |
|---|---|---|
runSmartAgent(RunSmartAgentRequest) |
POST /api/v2/smart-agents/run |
Run an agent as a new AI column (needs quoteId). |
listSmartAgents(String listId, Integer limit) |
GET /api/v2/smart-agents |
Recent agent runs. |
getSmartAgentResults(String smartAgentTaskId) |
GET /api/v2/smart-agents/{smart_agent_task_id} |
Per-lead output. |
EstimateCostResponse quote = cl.workspace.creditsEstimate(new EstimateCostRequest()
.tool("run_smart_agent").listId(list.getListId()).agentType("custom_ai").rowCount(50));
RunSmartAgentResponse run = cl.smartAgents.runSmartAgent(new RunSmartAgentRequest()
.listId(list.getListId())
.agentType("custom_ai")
.columnName("Personalized angle")
.prompt("In one sentence, suggest a cold-outreach angle for this lead.")
.maxRows(50)
.quoteId(quote.getQuoteId()));
SmartAgentResultsResponse results = cl.smartAgents.getSmartAgentResults(run.getSmartAgentTaskId());| Method | HTTP | Description |
|---|---|---|
exportCsv(ExportCsvRequest) |
POST /api/v2/export/csv/signed-url |
Export to CSV; returns a signed URL. |
exportJson(String listId, Integer limit, String cursor, List<String> columns) |
GET /api/v2/export/json |
Export rows inline as JSON. |
ExportCsvResponse signed = cl.export.exportCsv(new ExportCsvRequest().listId(list.getListId()));
System.out.println("download: " + signed.getDownloadUrl()); // valid until signed.getExpiresAt()
ExportJsonResponse data = cl.export.exportJson(list.getListId(), 500, null, null);
data.getLeads().forEach(System.out::println);Non-2xx responses throw the checked ApiException, exposing the status code and body:
import ai.cleanlist.client.ApiException;
try {
cl.leadLists.getList("does-not-exist");
} catch (ApiException e) {
switch (e.getCode()) {
case 404 -> System.out.println("no such list");
case 401 -> System.out.println("bad or missing API key");
default -> System.out.printf("API error %d: %s%n", e.getCode(), e.getResponseBody());
}
}List endpoints return a page plus an opaque cursor. Pass it back for the next page; a
null cursor means you've reached the end:
String cursor = null;
do {
LeadsPageResponse page = cl.leadLists.listLeadsInList(list.getListId(), 500, cursor);
page.getLeads().forEach(lead -> { /* ... */ });
cursor = page.getCursor();
} while (cursor != null && !cursor.isEmpty());The Cleanlist facade is optional sugar. Wire the generated pieces yourself if you prefer:
import ai.cleanlist.client.ApiClient;
import ai.cleanlist.client.Configuration;
import ai.cleanlist.client.api.PublicWorkspaceApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://api.cleanlist.ai");
client.setBearerToken("clapi_live_...");
PublicWorkspaceApi workspace = new PublicWorkspaceApi(client);
System.out.println(workspace.whoami());This SDK is generated with openapi-generator-cli
(java / okhttp-gson, pinned in openapitools.json). To refresh after an API change:
# 1. Drop the latest openapi/cleanse-api-v2.oas.json in place, then:
bash scripts/generate.sh # cleans operationIds, regenerates src/, re-applies the facade
mvn -q -DskipTests package # or ./gradlew build- Interactive API docs (OpenAPI): https://api.cleanlist.ai/docs
- Docs & guides: https://docs.cleanlist.ai
- Dashboard: https://app.cleanlist.ai
- Email: sal@cleanlist.ai
Licensed under the MIT License.