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

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: Quote with POST /credits/estimate (tool: "run_agent"), start a run with POST /agents/qualification/run or POST /agents/research/run, poll GET /agents/runs/{run_id}, then read GET /agents/runs/{run_id}/results. Up to 500 leads per run. The price per lead is your workspace’s price for the skill, listed by GET /agents.

Agents API (Skills)

The Agents API runs the Research and Qualification skills over the leads in a lead list, the same skills as on the Skills tab of a list’s Actions drawer. Results land in a column of the list and are readable here. These are not the automations on the Agents page (those are the GTM Agents API), and to run your own per-lead prompt, use Smart Agents.

EndpointMethodScopeCost
Run QualificationPOST /agents/qualification/runsmart_agents:writePer lead, from the quote
Run ResearchPOST /agents/research/runsmart_agents:writePer lead and research_scope, from the quote
Run statusGET /agents/runs/{run_id}smart_agents:readFree
Run resultsGET /agents/runs/{run_id}/resultssmart_agents:readFree
List agentsGET /agentssmart_agents:readFree
Create custom agentPOST /agents/customsmart_agents:writeFree
Update custom agentPATCH /agents/custom/{agent_id}smart_agents:writeFree
Agent profileGET /agents/profilesmart_agents:readFree

These endpoints are live on /api/v2 but aren’t in the served OpenAPI spec (/openapi-public-v2.json) yet, so the SDKs don’t wrap them. Call them over HTTP as shown here. They aren’t available to AppSumo-only workspaces (a paid CluGTM plan on top lifts this), which get 403 feature_gated.

The skills

AgentWhat each lead getsPrice
qualificationA 0 to 100 fit_score, a verdict (qualified, disqualified or needs_review) with reasons, a CRM check (already in your CRM or not) and any disqualification flagscredits_per_lead in GET /agents
researchLive web research on the company, the contact or both, set by research_scopecredits_per_lead_by_scope in GET /agents

Both skills score and write against your workspace’s agent profile: your ICP, personas, value props, competitors and exclusions. If verdicts look wrong, read the profile first.

Pricing

Each skill is priced per lead for your workspace, to a tenth of a credit, and the number in GET /agents is the number the run charges. Research has a different price for each research_scope. The default prices are in Skills; always read yours from GET /agents or the quote.

A run reserves the per-lead price times the leads, rounded up once to a whole credit (credits_reserved), and is charged the exact total, so the charge can be a fraction of a credit below the hold. The rest of the hold comes back when the run settles.

Run a skill

POST /agents/qualification/run

POST /agents/research/run

Scope: smart_agents:write

Both routes take the same body; Research adds research_scope.

Request body

FieldTypeNotes
list_idstringRequired. A list you can edit. A list you can’t access, or can only view, returns 404 list_not_found
lead_idsstring[]1 to 500 lead ids. Ids that aren’t in the list are dropped
task_idstringA cohort handle from a search or list read. Only leads that are in this list run; more than 500 of them returns 400 validation_error
scope"all"Every lead in the list. Over 500 leads returns 400 approval_required
research_scopeenumResearch only: company, contact or both (default both). Sets depth and price
quote_idstringRequired. From POST /credits/estimate; see below
idempotency_keystringOptional, 1 to 128 characters. A retry with the same key and the same inputs returns the original run instead of starting and billing a second one; the same key with different inputs returns 409 idempotency_conflict

Send exactly one of lead_ids, task_id or scope: "all"; none or more than one returns 422. scope picks leads; research_scope picks research depth.

Quote first

Estimate with tool: "run_agent", the list_id, and agent_type set to qualification or research. For Research, also send scope set to the research_scope you will run, even when that is the default both: the quote is signed for that depth, and a quote for another depth returns quote_mismatch.

The quote prices the whole list, whatever you select, so a run over a subset costs less than the quote. If the list grows after the quote so the run would reserve more than the quote, you get 400 spend_cap_exceeded; quote again. Quotes last 5 minutes and are single use.

# 1. Quote (Research over company and contact) curl -X POST https://api.cleanlist.ai/api/v2/credits/estimate \ -H "Authorization: Bearer clapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "tool": "run_agent", "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "agent_type": "research", "scope": "both" }' # 2. Run with the returned quote_id curl -X POST https://api.cleanlist.ai/api/v2/agents/research/run \ -H "Authorization: Bearer clapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "scope": "all", "research_scope": "both", "quote_id": "qte_v1_...", "idempotency_key": "research-2026-11-10-01" }'

For Qualification, quote with "agent_type": "qualification" and no scope, then call POST /agents/qualification/run without research_scope:

{ "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "lead_ids": ["b2d4f6a8-1c3e-4d5f-8a9b-0c1d2e3f4a5b"], "quote_id": "qte_v1_..." }

Response

{ "run_id": "smart-column-3f9c8e2b-7d14-4f6a-9c0e-1b2d3f4a5b6c", "agent": "qualification", "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "status": "pending", "lead_count": 120, "credits_reserved": 12, "credits_per_lead": 0.1, "status_url": "/api/v2/agents/runs/smart-column-3f9c8e2b-7d14-4f6a-9c0e-1b2d3f4a5b6c", "timestamp_ms": 1751404800000 }
FieldMeaning
run_idThe run handle. Poll it at status_url
lead_countLeads actually scheduled, after dropping ids outside the list
credits_reservedCredits held: credits_per_lead × lead_count, rounded up to a whole credit. A same-key retry reports the hold its first attempt opened
credits_per_leadThe per-lead price the hold used

The run starts in the background and the response returns at once with status: "pending". Dispatch is capped at 20 runs a minute per workspace (429 rate_limited).

Poll a run

GET /agents/runs/{run_id}

Scope: smart_agents:read · Cost: free

{ "run_id": "smart-column-3f9c8e2b-7d14-4f6a-9c0e-1b2d3f4a5b6c", "agent": "qualification", "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "status": "processing", "progress": 40, "total_leads": 120, "processed_leads": 48, "failed_leads": 1, "started_at": "2026-11-10T15:02:11", "completed_at": null, "results_url": "/api/v2/agents/runs/smart-column-3f9c8e2b-7d14-4f6a-9c0e-1b2d3f4a5b6c/results", "timestamp_ms": 1751404800000 }

status is pending or processing while the run works, and completed, failed or partial when it stops. Poll every few seconds until it stops; see Polling & Async. An unknown run id and one from another workspace both return 404 run_not_found.

Read results

GET /agents/runs/{run_id}/results

Scope: smart_agents:read · Cost: free

Query paramDefaultNotes
min_fit_scorenone0 to 100. Keeps rows scoring at least this; rows with no score are dropped
verdictnonequalified, disqualified or needs_review
sortfit_score_descOr fit_score_asc. Unscored rows always sort last
limit251 to 100
cursornoneFrom the previous page. A malformed cursor returns 400 invalid_cursor
{ "run_id": "smart-column-3f9c8e2b-7d14-4f6a-9c0e-1b2d3f4a5b6c", "agent": "qualification", "list_id": "9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04", "status": "completed", "total_rows": 120, "returned": 25, "counts_by_verdict": { "qualified": 41, "needs_review": 22, "disqualified": 56, "unscored": 1 }, "results": [ { "lead_id": "b2d4f6a8-1c3e-4d5f-8a9b-0c1d2e3f4a5b", "full_name": "Dana Reyes", "title": "VP of Revenue Operations", "company": "Acme Analytics", "fit_score": 82, "verdict": "qualified", "verdict_reasons": ["RevOps owner at a 300-person SaaS company"], "crm_status": { "is_existing": false, "matched_record": null }, "disqualification_flags": [], "analysis": "Acme raised a $40M Series B in March…", "analysis_truncated": true, "structured_failed": false, "error": null } ], "cursor": "25", "timestamp_ms": 1751404800000 }
FieldMeaning
total_rowsRows from this run, before min_fit_score and verdict filter them
counts_by_verdictVerdict counts over the whole run, never narrowed by the filters. unscored counts rows with no verdict yet
fit_score0 to 100, or null when the row isn’t scored yet or the skill could only return prose (structured_failed: true)
crm_statusThe CRM check: is_existing and matched_record, or an error of no_crm_connected or lookup_timeout
analysisThe skill’s write-up in Markdown, cut to 600 characters (analysis_truncated says when). The full text is in the list column and in exports
errorWhy this lead failed, if it did

Rows appear as leads finish, so a running job returns a partial set. Rows belong to the run that wrote them: if a later run of the skill rewrites a lead, that lead moves to the later run’s results. Research rows use the same shape: read analysis; fit_score and verdict stay null.

List agents

GET /agents

Scope: smart_agents:read · Cost: free

Lists the skills your workspace can run, with your prices, plus every custom agent saved in the workspace.

{ "agents": [ { "agent": "qualification", "name": "Qualification", "kind": "built_in", "description": "Score each lead against your org ICP and check whether they are already in your CRM. Returns a fit score, a verdict, reasons, and disqualification flags.", "credits_per_lead": 0.1, "run_endpoint": "/agents/qualification/run" }, { "agent": "research", "name": "Research", "kind": "built_in", "description": "Deep-research a lead's company and/or contact. `research_scope` controls both depth and price.", "credits_per_lead_by_scope": { "company": 2.9, "contact": 1.4, "both": 3.0 }, "run_endpoint": "/agents/research/run" }, { "agent": "custom", "name": "Series B fit", "kind": "custom", "description": "Scores against our Series B ICP", "agent_id": "7d2e9b41-...", "credits_per_lead": 0.2, "run_endpoint": null } ], "total": 3, "timestamp_ms": 1751404800000 }

The prices above are examples; yours come back in the response. Custom agents have run_endpoint: null: they run from the app (or as a smart agent with your prompt), not from this API.

Custom agents

A custom agent is a saved, reusable prompt for the workspace, the same as a saved prompt on the Skills tab. Only org admins can create or edit one; anyone else gets 403 forbidden. There’s no delete on the API.

POST /agents/custom

Scope: smart_agents:write · Returns 201

FieldTypeNotes
namestringRequired. Unique in the workspace; a duplicate returns 409 conflict
descriptionstringOptional, up to 500 characters
prompt_templatestringRequired. Up to 8,000 tokens; longer returns 400 prompt_too_long
curl -X POST https://api.cleanlist.ai/api/v2/agents/custom \ -H "Authorization: Bearer clapi_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Series B fit", "description": "Scores against our Series B ICP", "prompt_template": "For each lead, judge whether the company matches our Series B ICP and explain why in two sentences." }'
{ "agent_id": "7d2e9b41-...", "name": "Series B fit", "description": "Scores against our Series B ICP", "prompt_template": "For each lead, judge whether the company matches our Series B ICP and explain why in two sentences.", "prompt_tokens": 21, "created_at": "2026-11-10T15:00:00", "modified_at": "2026-11-10T15:00:00", "timestamp_ms": 1751404800000 }

PATCH /agents/custom/{agent_id}

Scope: smart_agents:write

Changes only the fields you send (name, description, prompt_template); an empty body returns 422. Send "description": null to clear the description. An agent_id from another workspace returns 404 not_found. Returns the same object as create.

Agent profile

GET /agents/profile

Scope: smart_agents:read · Cost: free

Returns the profile every Research and Qualification run is measured against. It’s read-only here; edit it on the Agents page Profile tab in the app.

{ "organization_id": "org_...", "website_url": "https://example.com", "linkedin_url": "https://www.linkedin.com/company/example", "profile_data": { "icp": "...", "personas": ["..."], "competitors": ["..."] }, "onboarding_completed_at": "2026-10-02T18:20:00", "last_edited_at": "2026-10-20T09:41:00", "timestamp_ms": 1751404800000 }

profile_data holds your ICP, personas, value props, competitors and exclusions. onboarding_completed_at is null until someone in the workspace approves the profile, and scores are weak until then. A workspace with no profile yet returns 404 not_found: complete agent onboarding in the app first.

Errors

CodeHTTPCause
validation_error400The selection resolved to no leads of the list, a cohort holds more than 500 of them, or the estimate got a Research scope other than company, contact or both
approval_required400scope: "all" on a list of more than 500 leads. Pass lead_ids batches of up to 500
quote_expired, quote_mismatch, quote_invalid, spend_cap_exceeded400Quote again with tool: "run_agent", the list_id, agent_type and (Research) scope
insufficient_credits400Not enough credits for the hold; buy more on the Plans page (app.clugtm.com/plans) or run a smaller batch
prompt_too_long400A prompt over 8,000 tokens
invalid_cursor400A results cursor that didn’t come from the previous page
forbidden403Creating or editing a custom agent without org-admin rights
feature_gated403AppSumo-only workspace
list_not_found404Unknown list, one you can’t access, or one shared with you as view-only
task_not_found404An unknown or expired cohort task_id (handles last an hour)
run_not_found404Unknown run id, or one from another workspace
not_found404Unknown custom agent, or no agent profile yet
quote_already_redeemed409Quotes are single use; quote again
idempotency_conflict409The idempotency_key was used with different inputs
conflict409A custom agent with that name already exists
rate_limited429More than 20 runs a minute in the workspace

See Errors & Rate Limits for every code.