Leads and campaigns
Leads live in lead lists, and dial sessions call one list at a time. A campaign groups lists and sessions for the 30-day abandonment rate. Consent records and your do-not-call list are checked for every lead before each dial.
Guide: Dial sessions.
Endpoints
| POST | /v1/lead_lists | Create a lead list |
| GET | /v1/lead_lists | List lead lists |
| POST | /v1/lead_lists/{id}/leads | Add leads |
| GET | /v1/lead_lists/{id}/leads | List leads in a list |
| GET | /v1/leads/{id} | Retrieve a lead |
| PATCH | /v1/leads/{id} | Update a lead |
| DELETE | /v1/leads/{id} | Delete a lead |
| POST | /v1/campaigns | Create a campaign |
| GET | /v1/campaigns/{id} | Retrieve a campaign |
| PATCH | /v1/campaigns/{id} | Update a campaign |
| GET | /v1/campaigns | List campaigns |
| POST | /v1/consents | Store a consent record |
| PATCH | /v1/compliance_settings | Update compliance settings |
| GET | /v1/compliance_settings/calling_hours | List calling hours by state |
| POST | /v1/dnc | Add to the do-not-call list |
| GET | /v1/dnc/{phone} | Check a number |
| POST | /v1/dnc_lists | Upload a state do-not-call list |
| GET | /v1/dnc_lists | List state do-not-call lists |
The lead object
| Field | Type | Description |
|---|---|---|
id | string | Unique ID, starts with led_. |
lead_list_id | string | The list the lead belongs to. |
phone | string | E.164 format, US numbers only. |
state | string | Two-letter state. Sets calling hours, call limits and the recording rule. |
name | string | null | Shown to the agent when the call connects. |
company | string | null | Shown to the agent. |
external_id | string | null | Your CRM ID. Returned on every event about the lead. |
consent_id | string | null | The consent record for this lead. |
registry_exception | object | null | A registry exception for this lead, or null. type is written_agreement or established_business_relationship. For written_agreement, consent_id points to the signed agreement. For established_business_relationship, basis is transaction or inquiry and date is the last purchase or transaction, or the inquiry. expires_at is set by us: 18 months after a transaction, 3 months after an inquiry. A lead with a current exception passes the National Do Not Call Registry check, and a state list check where that state allows the same exception. It never passes your internal do-not-call list. |
ivr | object | null | Phone tree target for this lead. Overrides the dial session default. |
status | string | queued, scheduled, in_call, completed, exhausted, held, do_not_call or invalid. See lead statuses. |
status_reason | string | null | For scheduled: callback, retry_delay, outside_calling_hours or state_call_limit. For held: state_unknown, no_consent, national_dnc or state_dnc. For do_not_call: internal_dnc. For invalid: line_disconnected. Block reasons match call.blocked. |
attempts | integer | Calls placed to this lead. |
caller_id | string | null | The number this lead sees. Set on the first call and kept for every call after it. |
caller_id_since | string | null | When caller_id was first used for this lead. |
last_call_id | string | null | The most recent call to this lead. |
last_called_at | string | null | When the lead was last dialed. |
next_call_at | string | null | When the lead is dialed next, for a scheduled lead. |
callback_at | string | null | A scheduled callback from a disposition. |
metadata | object | Up to 20 keys of your own. |
created_at | string | When the lead was added, ISO 8601. |
Create a lead list
Creates an empty list. Add leads to it with the next endpoint.
POST/v1/lead_lists
| Body parameters | Type | Description |
|---|---|---|
namerequired | string | Shown in the console. |
id | string | Your own ID, starting with lst_. Generated if you leave it out. |
campaign_id | string | The campaign the list belongs to. A list created without one gets a campaign of its own. |
skip_duplicates | boolean | Skip a lead whose phone is already in the list. Default true. |
metadata | object | Up to 20 keys. |
curl https://api.liveleg.dev/v1/lead_lists \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "lst_roofing_tx",
"name": "Roofing, Texas",
"campaign_id": "cmp_roofing_q4"
}'const res = await fetch('https://api.liveleg.dev/v1/lead_lists', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: "lst_roofing_tx",
name: "Roofing, Texas",
campaign_id: "cmp_roofing_q4"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/lead_lists",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"id": "lst_roofing_tx",
"name": "Roofing, Texas",
"campaign_id": "cmp_roofing_q4",
},
).json() {
"id": "lst_roofing_tx",
"object": "lead_list",
"name": "Roofing, Texas",
"campaign_id": "cmp_roofing_q4",
"skip_duplicates": true,
"counts": {
"queued": 0,
"scheduled": 0,
"in_call": 0,
"completed": 0,
"exhausted": 0,
"held": 0,
"do_not_call": 0,
"invalid": 0
},
"created_at": "2026-11-03T13:40:02Z"
} List lead lists
Returns your lead lists, newest first.
GET/v1/lead_lists
| Query parameters | Type | Description |
|---|---|---|
campaign_id | string | Lists in this campaign. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/lead_lists?campaign_id=cmp_roofing_q4" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/lead_lists?campaign_id=cmp_roofing_q4', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/lead_lists",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"campaign_id": "cmp_roofing_q4",
},
).json() {
"object": "list",
"data": [
{
"id": "lst_roofing_tx",
"object": "lead_list",
"name": "Roofing, Texas",
"campaign_id": "cmp_roofing_q4",
"skip_duplicates": true,
"counts": {
"queued": 1210,
"scheduled": 384,
"in_call": 0,
"completed": 171,
"exhausted": 52,
"held": 0,
"do_not_call": 14,
"invalid": 9
},
"created_at": "2026-11-03T13:40:02Z"
}
],
"has_more": false
} Add leads
Adds up to 500 leads per request. You can keep adding leads while a session is running. Valid leads are added even when others fail. Each failed lead is listed in rejected with its position and a code: invalid_phone, not_us_number or missing_phone. A lead whose phone is already in the list is counted in duplicates and skipped.
POST/v1/lead_lists/{id}/leads
| Body parameters | Type | Description |
|---|---|---|
leadsrequired | array | Up to 500 leads with the fields below. |
leads[].phonerequired | string | E.164 format, US numbers only. |
leads[].staterequired | string | Two-letter state. Required unless the area code maps to one state. Leads with no state are never dialed automatically. |
leads[].name | string | Shown to the agent when the call connects. |
leads[].company | string | Shown to the agent. |
leads[].external_id | string | Your CRM ID. Returned on every event about the lead. |
leads[].consent_id | string | A consent record. Required when the campaign has require_consent on. |
leads[].registry_exception | object | A written-agreement or established-business-relationship exception. See the lead object. |
leads[].callback_at | string | A time to call this lead, ISO 8601. Moves to the next open window if it falls outside calling hours. |
leads[].ivr | object | target and fallback for phone tree navigation on this lead. |
leads[].metadata | object | Up to 20 keys of your own. |
curl https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{
"phone": "+15125550147",
"state": "TX",
"name": "Dana Ruiz",
"company": "Ridgeline Roofing",
"external_id": "crm_88213"
},
{
"phone": "+15125550163",
"state": "TX",
"name": "Luis Ortega",
"external_id": "crm_88219"
},
{
"phone": "+1512555",
"state": "TX",
"external_id": "crm_88220"
}
]
}'const res = await fetch('https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
leads: [
{
phone: "+15125550147",
state: "TX",
name: "Dana Ruiz",
company: "Ridgeline Roofing",
external_id: "crm_88213"
},
{
phone: "+15125550163",
state: "TX",
name: "Luis Ortega",
external_id: "crm_88219"
},
{
phone: "+1512555",
state: "TX",
external_id: "crm_88220"
}
]
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"leads": [
{
"phone": "+15125550147",
"state": "TX",
"name": "Dana Ruiz",
"company": "Ridgeline Roofing",
"external_id": "crm_88213",
},
{
"phone": "+15125550163",
"state": "TX",
"name": "Luis Ortega",
"external_id": "crm_88219",
},
{
"phone": "+1512555",
"state": "TX",
"external_id": "crm_88220",
},
],
},
).json() {
"object": "lead_batch",
"lead_list_id": "lst_roofing_tx",
"added": 2,
"duplicates": 0,
"rejected": [
{
"index": 2,
"phone": "+1512555",
"code": "invalid_phone"
}
],
"data": [
{
"id": "led_44Hc2",
"external_id": "crm_88213",
"status": "queued"
},
{
"id": "led_44Hc3",
"external_id": "crm_88219",
"status": "queued"
}
]
} List leads in a list
Returns the leads in a list, newest first.
GET/v1/lead_lists/{id}/leads
| Query parameters | Type | Description |
|---|---|---|
status | string | queued, scheduled, in_call, completed, exhausted, held, do_not_call or invalid. |
external_id | string | The lead with this CRM ID. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads?status=scheduled&limit=50" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads?status=scheduled&limit=50', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"status": "scheduled",
"limit": 50,
},
).json() {
"object": "list",
"data": [
{
"id": "led_44Hc2",
"object": "lead",
"lead_list_id": "lst_roofing_tx",
"phone": "+15125550147",
"state": "TX",
"name": "Dana Ruiz",
"company": "Ridgeline Roofing",
"external_id": "crm_88213",
"consent_id": null,
"registry_exception": null,
"ivr": null,
"status": "scheduled",
"status_reason": "callback",
"attempts": 1,
"caller_id": "+15125550199",
"caller_id_since": "2026-11-03T15:42:00Z",
"last_call_id": "cal_8Jq2mT4x",
"last_called_at": "2026-11-03T15:42:00Z",
"next_call_at": "2026-11-06T20:00:00Z",
"callback_at": "2026-11-06T14:00:00-06:00",
"metadata": {},
"created_at": "2026-11-03T13:41:20Z"
}
],
"has_more": false
} Retrieve a lead
Returns the lead with its status, attempts, assigned caller ID and the next time it is dialed.
GET/v1/leads/{id}
curl https://api.liveleg.dev/v1/leads/led_44Hc2 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/leads/led_44Hc2', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/leads/led_44Hc2",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "led_44Hc2",
"object": "lead",
"lead_list_id": "lst_roofing_tx",
"phone": "+15125550147",
"state": "TX",
"name": "Dana Ruiz",
"company": "Ridgeline Roofing",
"external_id": "crm_88213",
"consent_id": null,
"registry_exception": null,
"ivr": null,
"status": "scheduled",
"status_reason": "callback",
"attempts": 1,
"caller_id": "+15125550199",
"caller_id_since": "2026-11-03T15:42:00Z",
"last_call_id": "cal_8Jq2mT4x",
"last_called_at": "2026-11-03T15:42:00Z",
"next_call_at": "2026-11-06T20:00:00Z",
"callback_at": "2026-11-06T14:00:00-06:00",
"metadata": {},
"created_at": "2026-11-03T13:41:20Z"
} Update a lead
Changes the fields you send. The phone number cannot change. Add a new lead for a new number. A held lead returns to queued once its state, consent record or registry exception is set.
PATCH/v1/leads/{id}
| Body parameters | Type | Description |
|---|---|---|
state | string | Two-letter state. |
name | string | Shown to the agent. |
company | string | Shown to the agent. |
external_id | string | Your CRM ID. |
consent_id | string | A consent record for this lead. |
registry_exception | object | type, basis, date, consent_id and note. See the lead object. Send null to remove it. |
callback_at | string | A time to call this lead, ISO 8601. |
ivr | object | Phone tree target for this lead. |
metadata | object | Up to 20 keys. Send an empty string to remove a key. |
curl -X PATCH https://api.liveleg.dev/v1/leads/led_44Hc2 \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"registry_exception": {
"type": "established_business_relationship",
"basis": "transaction",
"date": "2026-04-18",
"note": "Roof repair invoice 4471"
}
}'const res = await fetch('https://api.liveleg.dev/v1/leads/led_44Hc2', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
registry_exception: {
type: "established_business_relationship",
basis: "transaction",
date: "2026-04-18",
note: "Roof repair invoice 4471"
}
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/leads/led_44Hc2",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"registry_exception": {
"type": "established_business_relationship",
"basis": "transaction",
"date": "2026-04-18",
"note": "Roof repair invoice 4471",
},
},
).json() {
"id": "led_44Hc2",
"object": "lead",
"lead_list_id": "lst_roofing_tx",
"phone": "+15125550147",
"state": "TX",
"name": "Dana Ruiz",
"company": "Ridgeline Roofing",
"external_id": "crm_88213",
"consent_id": null,
"registry_exception": {
"type": "established_business_relationship",
"basis": "transaction",
"date": "2026-04-18",
"consent_id": null,
"note": "Roof repair invoice 4471",
"expires_at": "2027-10-18"
},
"ivr": null,
"status": "queued",
"status_reason": null,
"attempts": 0,
"caller_id": null,
"caller_id_since": null,
"last_call_id": null,
"last_called_at": null,
"next_call_at": null,
"callback_at": null,
"metadata": {},
"created_at": "2026-11-03T13:41:20Z"
} Delete a lead
Removes the lead from its list. It is not dialed again. Call records, consent records and do-not-call entries stay.
DELETE/v1/leads/{id}
curl -X DELETE https://api.liveleg.dev/v1/leads/led_44Hc2 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/leads/led_44Hc2', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.delete(
"https://api.liveleg.dev/v1/leads/led_44Hc2",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "led_44Hc2",
"object": "lead",
"deleted": true
} Create a campaign
A campaign groups the lead lists and dial sessions that call for one offer. The 30-day abandoned-call rate and the 3% cap are tracked per campaign. Calling hours, attempts per lead and the retry wait are also set per campaign.
POST/v1/campaigns
| Body parameters | Type | Description |
|---|---|---|
namerequired | string | Shown in the dashboard and on reports. |
id | string | Your own ID, starting with cmp_. Generated if you leave it out. |
calling_hours | object | days, start and end in the lead's local time, for example mon to fri, 09:00 to 18:00. Narrows the state and account rules. It never widens them. |
max_attempts | integer | Calls to a lead without reaching a person before the lead is exhausted. 1 to 20, default 6. |
retry_after_minutes | integer | Wait after an unanswered call before the lead is dialed again. Default 240. |
caller_id_pool | string | Default pool for sessions in this campaign. |
max_lines | integer | Upper limit on lines per agent for every session in the campaign. 1 to 5, default 5. |
require_consent | boolean | Skip leads without a consent record. Defaults to the account compliance setting. |
abandon_message_id | string | The identified message played on abandoned calls. Must name your business and give a callback number. Defaults to the account message. |
metadata | object | Up to 20 keys. |
curl https://api.liveleg.dev/v1/campaigns \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "cmp_roofing_q4",
"name": "Roofing Q4",
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"start": "09:00",
"end": "18:00"
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence"
}'const res = await fetch('https://api.liveleg.dev/v1/campaigns', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: "cmp_roofing_q4",
name: "Roofing Q4",
calling_hours: {
days: [
"mon",
"tue",
"wed",
"thu",
"fri"
],
start: "09:00",
end: "18:00"
},
max_attempts: 6,
retry_after_minutes: 240,
caller_id_pool: "pool_local_presence"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/campaigns",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"id": "cmp_roofing_q4",
"name": "Roofing Q4",
"calling_hours": {
"days": ["mon", "tue", "wed", "thu", "fri"],
"start": "09:00",
"end": "18:00",
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence",
},
).json() {
"id": "cmp_roofing_q4",
"object": "campaign",
"name": "Roofing Q4",
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"start": "09:00",
"end": "18:00"
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence",
"max_lines": 5,
"require_consent": true,
"abandon_message_id": "ann_ridgeline_abandon",
"abandonment": {
"rate_30d": 0,
"answered_30d": 0,
"abandoned_30d": 0,
"cap": 0.03,
"line_limit": "normal"
},
"lead_list_ids": [],
"metadata": {},
"created_at": "2026-10-01T15:10:44Z"
} Retrieve a campaign
Returns the campaign with its 30-day abandonment numbers. abandonment.line_limit is one_per_agent while the rate is at the cap. Dialing does not stop. compliance.cap_reached is sent with an email at the cap, and compliance.cap_cleared when normal line counts return.
GET/v1/campaigns/{id}
curl https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cmp_roofing_q4",
"object": "campaign",
"name": "Roofing Q4",
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"start": "09:00",
"end": "18:00"
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence",
"max_lines": 5,
"require_consent": true,
"abandon_message_id": "ann_ridgeline_abandon",
"abandonment": {
"rate_30d": 0.012,
"answered_30d": 4120,
"abandoned_30d": 49,
"cap": 0.03,
"line_limit": "normal"
},
"lead_list_ids": [
"lst_roofing_tx"
],
"metadata": {},
"created_at": "2026-10-01T15:10:44Z"
} Update a campaign
Changes the fields you send. Running sessions pick up the change on their next round.
PATCH/v1/campaigns/{id}
| Body parameters | Type | Description |
|---|---|---|
name | string | Shown in the dashboard and on reports. |
calling_hours | object | days, start and end in the lead's local time, for example mon to fri, 09:00 to 18:00. Narrows the state and account rules. It never widens them. |
max_attempts | integer | Calls to a lead without reaching a person before the lead is exhausted. 1 to 20, default 6. |
retry_after_minutes | integer | Wait after an unanswered call before the lead is dialed again. Default 240. |
caller_id_pool | string | Default pool for sessions in this campaign. |
max_lines | integer | Upper limit on lines per agent for every session in the campaign. 1 to 5, default 5. |
require_consent | boolean | Skip leads without a consent record. Defaults to the account compliance setting. |
abandon_message_id | string | The identified message played on abandoned calls. Must name your business and give a callback number. Defaults to the account message. |
metadata | object | Up to 20 keys. |
curl -X PATCH https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4 \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
"start": "09:00",
"end": "18:00"
}
}'const res = await fetch('https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
calling_hours: {
days: [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
start: "09:00",
end: "18:00"
}
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"calling_hours": {
"days": ["mon", "tue", "wed", "thu", "fri", "sat"],
"start": "09:00",
"end": "18:00",
},
},
).json() {
"id": "cmp_roofing_q4",
"object": "campaign",
"name": "Roofing Q4",
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
"start": "09:00",
"end": "18:00"
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence",
"max_lines": 5,
"require_consent": true,
"abandon_message_id": "ann_ridgeline_abandon",
"abandonment": {
"rate_30d": 0.012,
"answered_30d": 4120,
"abandoned_30d": 49,
"cap": 0.03,
"line_limit": "normal"
},
"lead_list_ids": [
"lst_roofing_tx"
],
"metadata": {},
"created_at": "2026-10-01T15:10:44Z"
} List campaigns
Returns your campaigns, newest first.
GET/v1/campaigns
| Query parameters | Type | Description |
|---|---|---|
line_limit | string | normal or one_per_agent. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/campaigns?line_limit=one_per_agent" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/campaigns?line_limit=one_per_agent', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/campaigns",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"line_limit": "one_per_agent",
},
).json() {
"object": "list",
"data": [
{
"id": "cmp_dental_recall",
"object": "campaign",
"name": "Recall visits",
"calling_hours": {
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"start": "09:00",
"end": "18:00"
},
"max_attempts": 6,
"retry_after_minutes": 240,
"caller_id_pool": "pool_local_presence",
"max_lines": 5,
"require_consent": true,
"abandon_message_id": "ann_ridgeline_abandon",
"abandonment": {
"rate_30d": 0.03,
"answered_30d": 1980,
"abandoned_30d": 60,
"cap": 0.03,
"line_limit": "one_per_agent"
},
"lead_list_ids": [
"lst_dental_recall"
],
"metadata": {},
"created_at": "2026-10-12T15:02:10Z"
}
],
"has_more": false
} Store a consent record
Stores the consent you collected for a lead: the type, when and where it was captured, the seller named and the exact text the person agreed to. With require_consent on, leads without a consent record are blocked. Records are kept with the call history and can be exported.
POST/v1/consents
| Body parameters | Type | Description |
|---|---|---|
phonerequired | string | The number the consent covers, E.164. |
lead_id | string | Links the record to a lead. Sets consent_id on the lead. |
typerequired | prior_express_written | prior_express | The kind of consent collected. |
captured_atrequired | string | When the person agreed, ISO 8601. |
sellerrequired | string | The business named in the consent text. |
textrequired | string | The exact words the person saw or heard. |
source_url | string | The page where consent was captured. |
ip | string | The IP address of the person who agreed. |
recorded_voice | boolean | true when the consent text covers calls with a recorded or artificial voice. The call screener line and the recorded consent announcement play only for leads with it, and never on calls to Washington numbers. Default false. |
curl https://api.liveleg.dev/v1/consents \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+17205550126",
"lead_id": "led_9Pq1",
"type": "prior_express_written",
"captured_at": "2026-10-28T18:22:10Z",
"seller": "Hollis Family Dental",
"text": "By submitting this form I agree that Hollis Family Dental may call me at the number above...",
"source_url": "https://hollisdental.example/quote",
"ip": "198.51.100.24",
"recorded_voice": false
}'const res = await fetch('https://api.liveleg.dev/v1/consents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone: "+17205550126",
lead_id: "led_9Pq1",
type: "prior_express_written",
captured_at: "2026-10-28T18:22:10Z",
seller: "Hollis Family Dental",
text: "By submitting this form I agree that Hollis Family Dental may call me at the number above...",
source_url: "https://hollisdental.example/quote",
ip: "198.51.100.24",
recorded_voice: false
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/consents",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"phone": "+17205550126",
"lead_id": "led_9Pq1",
"type": "prior_express_written",
"captured_at": "2026-10-28T18:22:10Z",
"seller": "Hollis Family Dental",
"text": "By submitting this form I agree that Hollis Family Dental may call me at the number above...",
"source_url": "https://hollisdental.example/quote",
"ip": "198.51.100.24",
"recorded_voice": False,
},
).json() {
"id": "cns_3Tg58Lw",
"object": "consent",
"phone": "+17205550126",
"lead_id": "led_9Pq1",
"type": "prior_express_written",
"captured_at": "2026-10-28T18:22:10Z",
"seller": "Hollis Family Dental",
"text": "By submitting this form I agree that Hollis Family Dental may call me at the number above...",
"source_url": "https://hollisdental.example/quote",
"ip": "198.51.100.24",
"recorded_voice": false,
"revoked_at": null,
"created_at": "2026-11-03T13:44:05Z"
} Update compliance settings
Sets the account rules that every pre-dial check uses. State rules always apply on top. Each subaccount has its own settings, and a subaccount cannot use its parent's registry subscription.
PATCH/v1/compliance_settings
| Body parameters | Type | Description |
|---|---|---|
calling_days | string[] | Days you allow. Default mon to sat. State rules still apply on top. |
require_consent | boolean | Skip any lead without a consent record. Default false. A campaign can override it. |
abandonment_cap | number | Campaign cap over 30 days. Default and maximum 0.03. |
abandon_message_id | string | The recorded message for abandoned calls. Must name your business and give a callback number. |
dnc.san | string | Your own National Do Not Call Registry Subscription Account Number (SAN). |
dnc.state_lists | string[] | States whose do-not-call list you hold. Upload each list with POST /v1/dnc_lists. Sales calls into a listed state are checked against your latest upload. |
curl -X PATCH https://api.liveleg.dev/v1/compliance_settings \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"calling_days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
"require_consent": true,
"dnc": {
"san": "SAN-48210773",
"state_lists": [
"TX",
"FL"
]
}
}'const res = await fetch('https://api.liveleg.dev/v1/compliance_settings', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
calling_days: [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
require_consent: true,
dnc: {
san: "SAN-48210773",
state_lists: [
"TX",
"FL"
]
}
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/compliance_settings",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"calling_days": ["mon", "tue", "wed", "thu", "fri", "sat"],
"require_consent": True,
"dnc": {
"san": "SAN-48210773",
"state_lists": ["TX", "FL"],
},
},
).json() {
"object": "compliance_settings",
"calling_days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
],
"require_consent": true,
"abandonment_cap": 0.03,
"abandon_message_id": "ann_ridgeline_abandon",
"dnc": {
"san": "SAN-48210773",
"state_lists": [
"TX",
"FL"
],
"registry_downloaded_at": "2026-10-28T06:00:00Z"
}
} List calling hours by state
Returns the calling window for every state, with state holidays, after your calling_days are applied.
GET/v1/compliance_settings/calling_hours
curl https://api.liveleg.dev/v1/compliance_settings/calling_hours \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/compliance_settings/calling_hours', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/compliance_settings/calling_hours",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"object": "list",
"data": [
{
"state": "PA",
"days": {
"mon_fri": "09:00-19:00",
"sat": "09:00-19:00",
"sun": null
},
"holidays": [
"2026-11-26",
"2026-12-25"
]
},
{
"state": "TX",
"days": {
"mon_fri": "09:00-21:00",
"sat": "09:00-21:00",
"sun": null
},
"holidays": []
}
],
"has_more": false
} Add to the do-not-call list
Adds a number to the account's internal do-not-call list. It is checked before every dial and every message. Numbers are also added when a lead presses the opt-out key, asks on a call not to be called again, or replies STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE or OPT OUT to a text. Each addition sends dnc.added.
POST/v1/dnc
| Body parameters | Type | Description |
|---|---|---|
phonerequired | string | The number, E.164. |
source | string | api by default. Other sources are set by us: call_phrase, keypress, text_reply. |
note | string | Why the number was added. |
curl https://api.liveleg.dev/v1/dnc \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+15125550147",
"note": "Asked by email not to be called"
}'const res = await fetch('https://api.liveleg.dev/v1/dnc', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone: "+15125550147",
note: "Asked by email not to be called"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/dnc",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"phone": "+15125550147",
"note": "Asked by email not to be called",
},
).json() {
"object": "dnc_entry",
"phone": "+15125550147",
"on_list": true,
"source": "api",
"note": "Asked by email not to be called",
"added_at": "2026-11-03T16:20:33Z"
} Check a number
Returns whether a number is on the do-not-call list, with the source and when it was added.
GET/v1/dnc/{phone}
curl https://api.liveleg.dev/v1/dnc/+15125550147 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/dnc/+15125550147', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/dnc/+15125550147",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"object": "dnc_entry",
"phone": "+15125550147",
"on_list": true,
"source": "text_reply",
"note": "Replied STOP",
"added_at": "2026-10-29T21:04:12Z"
} Upload a state do-not-call list
Uploads a state do-not-call list you downloaded with your own subscription to that state. Send the file as multipart form data: a CSV with one 10-digit number per line, or the state file as you received it. The newest upload for a state replaces the one before. Sales calls into the state are checked against it from the moment status is active, and a match is blocked with state_dnc.
POST/v1/dnc_lists
| Body parameters | Type | Description |
|---|---|---|
staterequired | string | Two-letter state, for example TX. |
filerequired | file | The list. CSV or the state file format. |
release_daterequired | string | The release date of the list, from the state, as YYYY-MM-DD. |
curl https://api.liveleg.dev/v1/dnc_lists \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-F state=TX \
-F release_date=2026-10-01 \
-F file=@tx_dnc_2026_10.csvimport { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('state', 'TX');
form.append('release_date', '2026-10-01');
form.append('file', new Blob([await readFile('tx_dnc_2026_10.csv')]), 'tx_dnc_2026_10.csv');
const res = await fetch('https://api.liveleg.dev/v1/dnc_lists', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LIVELEG_API_KEY}` },
body: form,
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/dnc_lists",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
data={
"state": "TX",
"release_date": "2026-10-01",
},
files={"file": open("tx_dnc_2026_10.csv", "rb")},
).json() {
"id": "dnl_6Rw20",
"object": "dnc_list",
"state": "TX",
"release_date": "2026-10-01",
"numbers": null,
"status": "processing",
"uploaded_at": "2026-11-03T16:41:09Z"
} List state do-not-call lists
Returns the active list for each state you hold, with its release date and number count.
GET/v1/dnc_lists
curl https://api.liveleg.dev/v1/dnc_lists \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/dnc_lists', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/dnc_lists",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"object": "list",
"data": [
{
"id": "dnl_6Rw20",
"object": "dnc_list",
"state": "TX",
"release_date": "2026-10-01",
"numbers": 412880,
"status": "active",
"uploaded_at": "2026-11-03T16:41:09Z"
},
{
"id": "dnl_5Qa17",
"object": "dnc_list",
"state": "FL",
"release_date": "2026-10-01",
"numbers": 1188204,
"status": "active",
"uploaded_at": "2026-10-02T14:10:51Z"
}
],
"has_more": false
}