Dial sessions

A dial session calls a lead list for one agent or a pool of agents, up to 5 lines per agent at a time. The first live person who answers is connected to the agent and the other lines are dropped.

How a session runs

Each round, we take the next leads from the list and run the pre-dial checks on each one: number lookup, your do-not-call list, calling hours in the lead's local time, state call limits and consent. Leads that pass are dialed at the same time, each from a local caller ID. Leads that fail get a call.blocked event with the rule and the next time they can be called.

Answering machine detection listens on every answered line. When it hears a person, that line is bridged to the agent and the rest are canceled. The agent hears the person about 1 second after they finish saying hello. When the call ends, the next round starts.

  • Every line rings for at least 15 seconds or 4 rings before it is dropped.
  • If two people answer and no agent is free, the second hears your identified message with a press-1 opt-out. That call counts toward the abandonment cap.
  • With lines=auto, the line count drops when the campaign's abandonment rate nears the cap and rises when fewer people answer.

Campaigns

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

ParametersTypeDescription
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. Defaults to the account message.
metadata object Up to 20 keys. Returned on events for the campaign.
POST/v1/campaigns
curl https://api.liveleg.dev/v1/campaigns \
  -H "Authorization: Bearer $LIVELEG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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"
  }'

Calling hours on a campaign use the lead's local time. The state rules in Compliance and your account settings still apply. A campaign window of 8 am to 9 pm is cut to 9 am to 7 pm for Pennsylvania leads.

Attach a campaign by setting campaign_id on the lead list. A dial session uses the list's campaign, or the campaign_id you pass when you start it.

Read the campaign to see its abandonment numbers. line_limit is normal or one_per_agent. At 3%, every session in the campaign dials one line per agent until the 30-day rate is back under the cap. Dialing does not stop. You get compliance.cap_reached and an email, then compliance.cap_cleared when normal line counts return.

GET/v1/campaigns/{id}

GET/v1/campaigns/cmp_roofing_q4
curl https://api.liveleg.dev/v1/campaigns/cmp_roofing_q4 \
  -H "Authorization: Bearer $LIVELEG_API_KEY"
Response
{
  "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,
  "abandonment": {
    "rate_30d": 0.012,
    "answered_30d": 4120,
    "abandoned_30d": 49,
    "cap": 0.03,
    "line_limit": "normal"
  },
  "created_at": "2026-10-01T15:10:44Z"
}

Lead lists

A lead list holds the leads a session dials. Each list belongs to one campaign. One list can feed several sessions at once, and each lead is dialed by one session at a time.

POST/v1/lead_lists

ParametersTypeDescription
id string Your own ID, starting with lst_. Generated if you leave it out.
namerequired string Shown in the dashboard.
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.
POST/v1/lead_lists
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"
  }'
Response
{
  "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-02T20:14:09Z"
}

Load leads

Add up to 500 leads per request. You can keep adding leads while a session is running. For larger imports, send several requests, each with its own Idempotency-Key.

POST/v1/lead_lists/{id}/leads

Lead fieldsTypeDescription
phonerequired string E.164 format, US numbers only.
staterequired string Two-letter state. Required unless the area code maps to one state. Leads with no state are never dialed automatically.
name string Shown to the agent when the call connects.
company string Shown to the agent.
external_id string Your CRM ID. Returned on every event about the lead.
consent_id string A consent record. Required when the campaign has require_consent on.
callback_at string A time to call this lead, ISO 8601. Moves to the next open window if it falls outside calling hours.
ivr object Phone tree target for this lead. Overrides the session default. See phone tree targets.
metadata object Up to 20 keys. Returned on every event about the lead.
POST/v1/lead_lists/lst_roofing_tx/leads
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": "+17205550126",
        "state": "CO",
        "name": "Sam Hollis",
        "company": "Hollis Family Dental",
        "external_id": "crm_88214"
      }
    ]
  }'
Response
{
  "object": "lead_batch",
  "lead_list_id": "lst_roofing_tx",
  "added": 2,
  "duplicates": 0,
  "rejected": [],
  "data": [
    {"id": "led_44Hc2", "external_id": "crm_88213", "status": "queued"},
    {"id": "led_44Hc3", "external_id": "crm_88214", "status": "queued"}
  ]
}

Valid leads are added even when others in the same request fail. Each failed lead is listed in rejected with its position in the request and a code: invalid_phone, not_us_number or missing_phone. Leads whose phone is already in the list are counted in duplicates and skipped.

Response
"rejected": [
  {"index": 7, "phone": "+1512555014", "code": "invalid_phone"},
  {"index": 12, "phone": "+447700900123", "code": "not_us_number"}
]

Change a lead with PATCH /v1/leads/{id} using the same fields. DELETE /v1/leads/{id} removes it from the list. Its call history stays.

Lead statuses

Each lead has a status. For a lead stopped by a pre-dial check, status_reason is the reason from call.blocked. Every change sends lead.status_changed. Filter the list by status to see what is left to dial.

GET/v1/lead_lists/{id}/leads

GET/v1/lead_lists/lst_roofing_tx/leads
curl "https://api.liveleg.dev/v1/lead_lists/lst_roofing_tx/leads?status=scheduled&limit=50" \
  -H "Authorization: Bearer $LIVELEG_API_KEY"
Response
{
  "id": "led_44Hc2",
  "object": "lead",
  "lead_list_id": "lst_roofing_tx",
  "campaign_id": "cmp_roofing_q4",
  "phone": "+15125550147",
  "state": "TX",
  "name": "Dana Ruiz",
  "company": "Ridgeline Roofing",
  "external_id": "crm_88213",
  "status": "scheduled",
  "status_reason": "retry_delay",
  "attempts": 2,
  "caller_id": "+15125550199",
  "caller_id_since": "2026-11-02T21:04:18Z",
  "last_call_id": "cal_7Hd1aP2c",
  "last_called_at": "2026-11-03T15:02:11Z",
  "next_call_at": "2026-11-03T19:02:11Z",
  "created_at": "2026-11-02T20:15:30Z"
}
StatusMeaning
queuedWaiting for its turn in list order.
scheduledHas a next_call_at in the future. status_reason is callback, retry_delay, outside_calling_hours or state_call_limit.
in_callA line is ringing or the lead is talking with an agent.
completedReached a person and the call has a disposition with no callback time.
exhaustedReached max_attempts without a person answering.
heldNot dialed until you change the lead or the data behind the block. status_reason is state_unknown, no_consent, national_dnc or state_dnc. Fix it with PATCH and the lead returns to queued.
do_not_callThe number is on your do-not-call list or opted out. status_reason is internal_dnc.
invalidNumber lookup found the line disconnected. status_reason is line_disconnected.

caller_id is the number this lead sees on every call, and caller_id_since is when it was first used. See Caller ID. State call limits count calls from all your numbers. When no lead in a list can be dialed now, you get lead_list.emptied with the counts by status.

Start a session

One request per agent. The agent must be connected in the browser or on a SIP phone before dialing starts; see Agent calling.

POST/v1/dial_sessions

ParametersTypeDescription
agent_idrequired string The agent who takes the calls. Use agent_pool_id instead to share one session across a pool of agents.
agent_pool_id string Agents in the pool share answered calls. A second live person goes to the next free agent.
lead_list_idrequired string The list to dial. Leads are dialed in list order, with scheduled callbacks first.
lines auto | 1-5 Lines per agent. auto sets the count from the live answer rate and the campaign abandonment rate. Default auto.
max_lines integer Upper limit when lines is auto. 1 to 5, default 5.
caller_id_pool string The pool caller IDs are picked from. See caller ID selection.
campaign_id string The campaign the session counts toward for the 30-day abandonment rate, calling hours and attempts. Defaults to the lead list's campaign.
amd boolean Answering machine detection. Default true.
recording smart | off Smart Recording. Default smart.
transcription off | post_call | live Transcripts for bridged calls. Default off.
metadata object Up to 20 keys. Returned on every event for the session.
POST/v1/dial_sessions
curl https://api.liveleg.dev/v1/dial_sessions \
  -H "Authorization: Bearer $LIVELEG_API_KEY" \
  -H "Idempotency-Key: agent-114-morning" \
  -d agent_id=agt_114 \
  -d lead_list_id=lst_roofing_tx \
  -d lines=auto \
  -d caller_id_pool=pool_local_presence \
  -d amd=true \
  -d recording=smart
Response
{
  "id": "dls_31Ka9",
  "object": "dial_session",
  "status": "running",
  "livemode": true,
  "agent_id": "agt_114",
  "lead_list_id": "lst_roofing_tx",
  "campaign_id": "cmp_roofing_q4",
  "lines": "auto",
  "max_lines": 5,
  "current_lines": 3,
  "caller_id_pool": "pool_local_presence",
  "amd": true,
  "recording": "smart",
  "totals": {"dialed": 0, "answered": 0, "bridged": 0, "blocked": 0},
  "created_at": "2026-11-03T14:00:02.118Z"
}

Pause, resume and stop

Pausing lets the lines that are already ringing finish and places no new ones. The agent stays connected. Stopping ends the session and returns the totals.

curl
curl -X POST https://api.liveleg.dev/v1/dial_sessions/dls_31Ka9/pause \
  -H "Authorization: Bearer $LIVELEG_API_KEY"

curl -X POST https://api.liveleg.dev/v1/dial_sessions/dls_31Ka9/resume \
  -H "Authorization: Bearer $LIVELEG_API_KEY"

curl -X POST https://api.liveleg.dev/v1/dial_sessions/dls_31Ka9/stop \
  -H "Authorization: Bearer $LIVELEG_API_KEY"

A session also stops on its own when the agent has been idle for 15 minutes, the list is empty, or calling hours have closed for every lead left. The reason is on the dial_session.stopped event.

Events

A session sends dial_session.* events for its own state, including dial_session.lines_changed when auto lines go up or down. It sends leg.* events for each line, call.bridged when a person is connected and lead.status_changed as leads move through the list. The full list is in Webhooks and events.

Event
{
  "id": "evt_2Wc81Lk0",
  "type": "call.bridged",
  "created_at": "2026-11-03T15:42:07.412Z",
  "livemode": true,
  "data": {
    "call_id": "cal_8Jq2mT4x",
    "dial_session_id": "dls_31Ka9",
    "agent_id": "agt_114",
    "lead_id": "led_44Hc2",
    "to": "+15125550147",
    "from": "+15125550199",
    "amd_result": "human",
    "speech_ended_to_bridge_ms": 980,
    "legs_canceled": 2
  }
}