Skip to content

Lists and import

A list is a batch of contacts meant for a campaign. It is the only door for cold outreach: contacts go through WhatSetter’s verification and paced, anti-ban sending, never through a direct send.

Method Path Scope
GET /lists lists:read
POST /lists lists:write
POST /lists/{id}/leads lists:write
Terminal window
curl -X POST "https://app.whatsetter.com/api/v1/lists" \
-H "Authorization: Bearer ws_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"CRM sync, hot leads"}'
{
"data": {
"id": "66f1a2b3c4d5e6f7a8b9c0a1",
"name": "CRM sync, hot leads",
"status": "ready",
"total_rows": 0,
"valid_leads": 0,
"invalid_leads": 0,
"created_at": "2026-09-19T08:00:00.000Z"
}
}

name is 1 to 255 characters. A workspace holds up to 200 lists; past that, 429 quota_exceeded.

Up to 500 leads per call. Each lead needs a phone in international format. name, email and custom_variables are optional.

Terminal window
curl -X POST "https://app.whatsetter.com/api/v1/lists/66f1a2b3c4d5e6f7a8b9c0a1/leads" \
-H "Authorization: Bearer ws_live_…" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{ "phone": "+33612345678", "name": "Julien", "email": "julien@example.com",
"custom_variables": { "city": "Lyon", "plan": "Pro" } },
{ "phone": "+33698765432", "name": "Marie" }
]
}'
{
"data": {
"imported": 2,
"duplicates_in_list": 0,
"rejected": [],
"lead_ids": ["66f1a2b3c4d5e6f7a8b9c0b1", "66f1a2b3c4d5e6f7a8b9c0b2"]
}
}

The status is 201 as soon as at least one lead was importable. lead_ids are the imported rows, in the order they were created. name and email are cut at 255 characters.

Case What happens
Same number twice in the batch The first one is kept, the other is listed in rejected with reason duplicate_in_batch.
Number already in this list Not created again, counted in duplicates_in_list.
Invalid number Listed in rejected with a reason starting with invalid_phone. A valid number is 8 to 15 digits, with or without +, spaces, dots, dashes or parentheses.
custom_variables is not an object Rejected with reason custom_variables must be an object.
custom_variables over 2000 characters once serialized Rejected with reason custom_variables exceeds 2000 characters.

rejected entries carry the index of the lead in your array, so you can log or retry precisely.

Every key of custom_variables becomes a per-lead variable your agent can use in its first message, written {{lead:key}} in the prompt editor. Keys are normalized to a slug: accents removed, lowercase, anything that is not a letter or digit becomes _. Ville du client and ville_du_client are the same variable.

With the example above, the prompt can say {{lead:city}} and {{lead:plan}}. A key missing on a given lead is reported as not provided to the agent, which is told never to invent a value.

GET /lists returns the 100 most recent lists of the workspace, newest first, in data. There is no cursor on this endpoint.

{
"data": [
{
"id": "66f1a2b3c4d5e6f7a8b9c0a1",
"name": "CRM sync, hot leads",
"status": "ready",
"total_rows": 2,
"valid_leads": 0,
"invalid_leads": 0,
"created_at": "2026-09-19T08:00:00.000Z"
}
]
}

valid_leads and invalid_leads are filled as WhatSetter verifies the numbers after import.

Importing does not send anything. The list is picked up by the campaign it is attached to in the dashboard: rows are verified, then sent at a human pace, within the number’s daily quota. Attach the list from your campaign in the dashboard, then follow the leads with GET /leads?campaign_id=.

When a person just signed up on your funnel and expects a message now, use POST /campaigns/{id}/leads instead. It sends a personalized first message right away, with the same anti-ban protections, and lets you attach an external_reference.

HTTP code When
401 missing_api_key, invalid_api_key Key absent, unknown or revoked.
403 insufficient_scope The key lacks lists:read or lists:write.
404 not_found Unknown list, or a list of another workspace.
422 validation_error Missing or too long name; leads not an array of 1 to 500; no importable lead in the batch (the message gives the first reason).
429 quota_exceeded 200 lists already exist in the workspace.
429 rate_limited Too many requests this minute. Honor Retry-After.