Pre-dial checks
Every dial passes the same checks at the moment it is placed. Dial sessions and single calls run them the same way. A lead that fails a check is not dialed, and you get a call.blocked event with the reason.
What runs before every dial
The checks run in this order. The first one that fails stops the dial.
| Check | Passes when |
|---|---|
| Lead state | The lead has a state, from the lead or from an area code that maps to one state. |
| Line type and status | A lookup of the number. Disconnected numbers are not dialed. |
| Do-not-call lists | Your internal list, the National Do Not Call Registry and the state lists you hold. |
| Calling hours | The lead's local time is inside the allowed window for their state and your calling days. |
| State call limits | The lead has not reached the limit for their state, counting calls from all your numbers. |
| Consent record | With require_consent on, the lead has a consent record. |
The checks run again at each dial. A lead that was outside calling hours in the morning passes in the afternoon. A number added to your do-not-call list a minute ago is blocked on the next dial.
Line type and status
A Number Lookup returns the line type and whether the number is active. Disconnected numbers are blocked with line_disconnected. The line type is saved on the call record.
Do-not-call lists
- Your internal do-not-call list. It holds numbers added through the API, opt-out key presses, requests on a call and STOP replies. It is checked on every call, sales or not.
- The National Do Not Call Registry. Sales calls to consumers are checked with your own subscription, using your SAN. We download the registry with it at least every 31 days.
- State do-not-call lists you hold. You subscribe to each state and upload its list to us. Sales calls into that state are checked against your latest upload.
Subaccounts check their own internal list and the client's own registry and state subscriptions. A subaccount cannot use its parent's SAN or state lists.
PATCH/v1/compliance_settings
| Parameters | Type | Description |
|---|---|---|
dnc.san | string | Your own National Do Not Call Registry Subscription Account Number (SAN). Each business uses its own. A subaccount sets the client's SAN. |
dnc.state_lists | string[] | States whose do-not-call list you hold. Upload each list with POST /v1/dnc_lists. |
curl -X PATCH https://api.liveleg.dev/v1/compliance_settings \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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({
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={
"dnc": {
"san": "SAN-48210773",
"state_lists": ["TX", "FL"],
},
},
).json() Upload a state list
Download each release from the state with your subscription, then upload the file. Send it as multipart form data with the state and the release date. The newest upload for a state replaces the one before, and GET /v1/dnc_lists shows the release date of each list you hold.
POST/v1/dnc_lists
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() Registry exceptions
A lead on the National Do Not Call Registry can still get a sales call from you in two cases. Mark the lead with registry_exception when you add it or with PATCH /v1/leads/{id}.
| type | When it applies | Fields |
|---|---|---|
written_agreement | The person signed a written agreement to get calls from you at this number. | consent_id of the signed agreement. A lead whose consent record is prior_express_written passes the same way. |
established_business_relationship | The person bought from you or did business with you in the last 18 months, or asked you about a product or service in the last 3 months. | basis transaction or inquiry, date, note |
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() - We set
expires_atfrom the date: 18 months after a transaction, 3 months after an inquiry. After that the lead is held withnational_dncagain. - State lists use the exceptions in each state's law. An exception passes a state list check only where that state allows it.
- An exception never passes your internal do-not-call list. A request not to be called ends the relationship exception for that number.
- The exception used is saved on the call record as
registry_exception, with the type and dates at the time of the call.
Calling hours
We use the lead's local time from the state on the lead, or from the area code when it maps to one state. The window is the stricter of the state rule and your calling_days. The rules by state are in Compliance. A block for calling hours carries next_allowed_at, the next time the window opens for that lead.
State call limits
Florida, Oklahoma and Maryland allow 3 sales calls to a person in 24 hours. The count includes calls from every number in the account or subaccount. Each lead keeps seeing the same number, and changing numbers does not reset the count. Oregon allows 3 unless the person is a customer. Debt collection accounts can turn on the limit of 7 calls in 7 days.
Consent records
With require_consent on, a lead without a consent record is blocked with no_consent. Store records with POST /v1/consents and link them to the lead with consent_id. See Consent records.
The call.blocked event
Each blocked lead sends one call.blocked event. reason is the check that failed. rule names the exact rule, with the state where it applies. next_allowed_at is the earliest time the lead passes that check, or null when it does not pass on its own.
{
"id": "evt_8Bk31Fs9",
"type": "call.blocked",
"created_at": "2026-11-03T21:14:05.882Z",
"livemode": true,
"data": {
"lead_id": "led_71Fm4",
"phone": "+13055550184",
"state": "FL",
"dial_session_id": "dls_31Ka9",
"campaign_id": "cmp_roofing_q4",
"reason": "state_call_limit",
"rule": "call_limit.FL",
"calls_in_period": 3,
"next_allowed_at": "2026-11-04T15:02:11Z"
}
} A single call that fails a check returns 422 with the same fields.
{
"error": {
"code": "blocked_by_rule",
"message": "The number is on your do-not-call list.",
"reason": "internal_dnc",
"rule": "dnc.internal",
"next_allowed_at": null,
"request_id": "req_6tB09kLm"
}
} Read a block
A blocked lead with next_allowed_at stays in the list and is dialed again after that time. A lead without it is skipped until you change the lead or the data behind the block.
| reason | rule | Meaning | What to do | Retried |
|---|---|---|---|---|
state_unknown | lead.state | No state on the lead and the area code covers more than one state. | Add state to the lead. | No |
line_disconnected | lookup.line_status | The number is disconnected. | Remove the lead or update the number. | No |
internal_dnc | dnc.internal | The number is on your do-not-call list. | Leave the lead out of future lists. | No |
national_dnc | dnc.national | The number is on the National Do Not Call Registry and the call is a sales call to a consumer. | Attach a consent record that covers the call, set a registry exception, or remove the lead. | No |
state_dnc | dnc.state.TX | The number is on a state do-not-call list you hold. | Same as national_dnc. | No |
outside_calling_hours | calling_hours.PA | The lead's local time is outside the window. | Nothing. The lead is called again after next_allowed_at. | Yes |
state_call_limit | call_limit.FL | The lead has had the state's limit of sales calls in the period. | Nothing. The lead is called again after next_allowed_at. | Yes |
no_consent | consent.required | require_consent is on and the lead has no consent record. | Store the consent record and set consent_id on the lead. | No |
A dial session counts blocked leads in totals.blocked.
Price
The checks are part of the $0.12 rate per answered minute. $0.02 of each answered minute pays for them. Blocked leads and checks on dials that are not answered cost nothing.