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_smart_agent"), start the run with POST /smart-agents/run, then poll GET /smart-agents/{smart_agent_task_id} for per-lead results. Up to 500 leads per run. Price comes from the quote: cold_intro_email 3 per lead, presets 1 per lead, custom_ai priced from its prompt.
Smart Agents
A smart agent runs one AI instruction on each lead in a lead list and writes the result into a column of that list, one AI call per lead. In the app, the same per-row work is done with Skills in the Actions drawer; smart agents are not the automations on the Agents page.
| Endpoint | Method | Scope | Cost |
|---|---|---|---|
| Run | POST /smart-agents/run | smart_agents:write | Per lead, from the quote |
| List runs | GET /smart-agents | smart_agents:read | Free |
| Get results | GET /smart-agents/{smart_agent_task_id} | smart_agents:read | Free |
Agent types
agent_type | What it does | Needs prompt? | Price per lead |
|---|---|---|---|
custom_ai (default) | Runs your own instruction on each lead | Yes | Priced from the prompt (see below) |
cold_intro_email | Drafts a personalized cold intro email from your email template | No | 3 |
title_normalizer | Normalizes job titles to one standard line | No | 1 |
company_intel | Summarizes key company intelligence in 3 to 5 bullets | No | 1 |
pain_point_hypothesis | Suggests 3 to 5 likely business pain points | No | 1 |
The three presets carry their own instruction and ignore prompt.
Pricing
Always read the price from the quote's estimated_cost:
custom_aiis priced like a Custom skill in the app: from an estimate of the work yourpromptasks for, so a short classification prompt costs less than a research prompt. The run total is rounded up once to a whole credit.cold_intro_emailis 3 credits per lead.- Presets are 1 credit per lead.
The quote always shows the price that applies to your workspace.
Run a smart agent
POST /smart-agents/run
Scope: smart_agents:write
Request body
| Field | Type | Notes |
|---|---|---|
list_id | string | Required. A list you can edit |
agent_type | enum | custom_ai (default), cold_intro_email, title_normalizer, company_intel, pain_point_hypothesis |
column_name | string | Required. Name of the column that stores the output (up to 100 characters). Cold intro emails use the built-in email column |
prompt | string | Required for custom_ai. The instruction applied to each lead |
template_id | string | cold_intro_email only: email template id. Defaults to your workspace's active default template |
lead_scope | enum | subset (default) runs on up to max_rows leads; all selects the whole list |
max_rows | integer | 1 to 10,000, default 100. Used with lead_scope: "subset" |
overwrite | boolean | Default false: leads that already have a value in this column are skipped. true replaces existing values |
quote_id | string | Required. From POST /credits/estimate; see below |
500 leads per run. If the selection is over 500 leads, the run is refused with 400 approval_required. Use lead_scope: "subset" with max_rows of 500 or fewer, and run larger lists in batches.
Quote first
Estimate with tool: "run_smart_agent" and the same list_id, agent_type, column_name, prompt, template_id, lead_scope, max_rows and overwrite you'll send to the run. Change any of them and the quote no longer matches.
# 1. Quote
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_smart_agent",
"list_id": "LIST_ID",
"agent_type": "custom_ai",
"column_name": "Fit Research",
"prompt": "In two sentences, explain why this lead is or is not a strong outbound fit.",
"lead_scope": "subset",
"max_rows": 100
}'
# 2. Run with the returned quote_id
curl -X POST https://api.cleanlist.ai/api/v2/smart-agents/run \
-H "Authorization: Bearer clapi_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"list_id": "LIST_ID",
"agent_type": "custom_ai",
"column_name": "Fit Research",
"prompt": "In two sentences, explain why this lead is or is not a strong outbound fit.",
"lead_scope": "subset",
"max_rows": 100,
"quote_id": "qte_v1_..."
}'Response
{
"smart_agent_task_id": "run_7c2a91",
"smart_agent_task_ids": { "custom_ai": "run_7c2a91" },
"status": "pending",
"list_id": "LIST_ID",
"column_name": "Fit Research",
"estimated_cost": 40,
"timestamp_ms": 1751404800000
}The run starts in the background. Use smart_agent_task_id to poll. (The envelope's task_id is kept for older clients; new code should read smart_agent_task_id.) The credits for the run are taken when it starts, and estimated_cost shows the expected charge.
With an SDK: smart_agents.run_smart_agent(...) and smart_agents.get_smart_agent_results(...). See the SDKs.
Cold intro emails
cold_intro_email needs two things, or it returns 400 validation_error:
- Enriched leads. Every lead in the selection must be enriched first. Run bulk enrichment, then get a new quote.
- An active email template in your workspace, or a
template_id.
List runs
GET /smart-agents
Scope: smart_agents:read · Cost: free
| Query param | Default | Notes |
|---|---|---|
list_id | none | Only runs for this list. Omit for recent runs across every list you can see |
limit | 20 | 1 to 100, most recent first |
{
"runs": [
{
"task_id": "run_7c2a91",
"list_id": "LIST_ID",
"column_name": "Fit Research",
"agent_type": "custom_ai",
"status": "completed",
"total": 100,
"processed": 100,
"failed": 2,
"created_at": "2026-07-01T20:40:00Z"
}
],
"total": 1
}Get results
GET /smart-agents/{smart_agent_task_id}
Scope: smart_agents:read · Cost: free
Poll until status is completed or failed (other states: pending, processing). The results array fills in as leads finish, so a running job returns a partial set.
{
"smart_agent_task_id": "run_7c2a91",
"status": "completed",
"progress": 100,
"total": 100,
"succeeded": 98,
"failed": 2,
"results": [
{ "lead_id": "b2d4f6a8-...", "value": "Acme raised a Series B in March and is hiring SDRs; strong fit.", "error": null }
]
}The output also appears as a column in the list in the app, and exports include it by default (include_smart_agents).
Errors
| Code | HTTP | Cause |
|---|---|---|
validation_error | 400 | Missing prompt for custom_ai, no eligible leads (empty list, or every lead already has a value and overwrite is false), unenriched leads or no template for cold_intro_email |
approval_required | 400 | Selection over 500 leads |
quote_expired, quote_mismatch, quote_invalid, spend_cap_exceeded | 400 | Quote again with exactly the fields you'll send |
quote_already_redeemed | 409 | Quotes are single use; quote again |
insufficient_credits | 400 | Not enough credits; buy more on the Plans page (app.cleanlist.ai/plans) |
feature_not_available | 501 | The agent_type isn't enabled |
list_not_found | 404 | Unknown list, or a private list you can't access |
list_not_editable | 403 | The list is shared with you as view-only |
See Errors & Rate Limits for every code.