Calls
Every connected conversation is a call with its own ID. Dial sessions create calls for you. You can also place a single call for a connected agent, act on a live call from your app, and read the record afterwards with every leg, timing and cost.
Guide: Call control.
Endpoints
| POST | /v1/calls | Place a single call |
| GET | /v1/calls/{id} | Retrieve a call |
| GET | /v1/call_records | List call records |
| POST | /v1/calls/{id}/hold | Hold a call |
| POST | /v1/calls/{id}/unhold | Take a call off hold |
| POST | /v1/calls/{id}/mute | Mute the agent |
| POST | /v1/calls/{id}/unmute | Unmute the agent |
| POST | /v1/calls/{id}/dtmf | Send digits |
| POST | /v1/calls/{id}/transfer | Transfer a call |
| POST | /v1/calls/{id}/transfer/complete | Complete a warm transfer |
| POST | /v1/calls/{id}/transfer/cancel | Cancel a transfer |
| POST | /v1/calls/{id}/conference | Add people to a call |
| DELETE | /v1/calls/{id}/participants/{participant_id} | Remove a participant |
| POST | /v1/calls/{id}/forward | Forward a call |
| POST | /v1/calls/{id}/hangup | Hang up a call |
| POST | /v1/calls/{id}/disposition | Save a disposition |
| POST | /v1/calls/{id}/streams | Start a stream |
| DELETE | /v1/calls/{id}/streams/{stream_id} | Stop a media stream |
| GET | /v1/callbacks | List scheduled callbacks |
| DELETE | /v1/callbacks/{id} | Cancel a callback |
The call object
| Field | Type | Description |
|---|---|---|
id | string | Unique ID, starts with cal_. |
dial_session_id | string | null | The dial session that placed the call. null for single and inbound calls. |
account_id | string | The account or subaccount the call belongs to. |
direction | string | outbound or inbound. |
from | string | The caller ID used. The same number this lead saw on earlier calls. |
to | string | The number called. |
agent_id | string | null | The agent on the call. |
lead_id | string | null | The lead the call belongs to. |
registry_exception | object | null | The registry exception that let the call through the do-not-call check, with type, basis, date and expires_at as they were at the time of the call. null when none was used. |
status | string | queued, ringing, in_progress, completed, no_answer or failed. |
on_hold | boolean | true while the prospect is on hold. |
muted | boolean | true while the agent is muted. |
legs | array | Every leg placed for the call, with id, to, result and rang_ms. |
amd_result | string | human, machine_voicemail, phone_tree or unknown. |
speech_ended_to_bridge_ms | integer | Time from the end of the prospect's hello to the bridge. The prospect hears the agent about 1 second after they finish saying hello (median, our network). |
ivr_path | array | null | The phone tree steps taken, when navigation ran. |
hold_seconds | integer | null | Time on hold inside a phone tree. |
answered_at | string | null | When the line was answered. |
bridged_at | string | null | When the prospect was connected to the agent. |
ended_at | string | null | When the call ended. |
hangup_by | string | null | prospect, agent or system. |
billed_seconds | integer | Seconds billed. Only time a live person was connected to the agent. |
disposition | string | null | The outcome code you saved. |
recording_id | string | null | The recording, when one was made. |
rate_per_minute | string | The per-minute rate applied. |
amount | string | The talk time at the per-minute rate, in US dollars. |
charges | array | Every charge for the call with item, quantity and amount, including the talk time and add-ons such as transcripts. |
surcharges | array | Carrier surcharges for the call, each with id and amount. |
total | string | Sum of charges and surcharges. |
Place a single call
Dials one number for an agent who is already connected. It runs the same pre-dial checks, detection and recording as a dial session. A call that fails a check returns 422 blocked_by_rule and is not placed.
POST/v1/calls
| Body parameters | Type | Description |
|---|---|---|
torequired | string | The number to call, E.164. |
from | string | A caller ID you own. Leave it out and set caller_id_pool to have one picked for the lead. |
caller_id_pool | string | Pool to pick the caller ID from. |
agent_idrequired | string | The connected agent who takes the call. |
lead_id | string | Links the call to a lead for calling hours, consent and callback routing. |
amd | boolean | Answering machine detection. Default true. |
amd_on_machine | hangup | notify | What happens when detection hears a machine. Default hangup. |
recording | smart | off | Default smart. |
ivr | object | Phone tree navigation, with the same fields as on a dial session. |
metadata | object | Up to 20 keys. Returned on every event for the call. |
curl https://api.liveleg.dev/v1/calls \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+13035550112",
"caller_id_pool": "pool_local_presence",
"agent_id": "agt_207",
"lead_id": "led_9Pq1",
"amd": true,
"recording": "smart"
}'const res = await fetch('https://api.liveleg.dev/v1/calls', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: "+13035550112",
caller_id_pool: "pool_local_presence",
agent_id: "agt_207",
lead_id: "led_9Pq1",
amd: true,
recording: "smart"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"to": "+13035550112",
"caller_id_pool": "pool_local_presence",
"agent_id": "agt_207",
"lead_id": "led_9Pq1",
"amd": True,
"recording": "smart",
},
).json() {
"id": "cal_5Rk03Vn",
"object": "call",
"dial_session_id": null,
"account_id": "acc_ridgeline",
"direction": "outbound",
"from": "+13035550181",
"to": "+13035550112",
"agent_id": "agt_207",
"lead_id": "led_9Pq1",
"status": "queued",
"amd": true,
"recording": "smart",
"created_at": "2026-11-03T16:10:44.208Z"
} Retrieve a call
Returns a live or finished call. After the call ends it includes every leg, the detection result, billed seconds and cost.
GET/v1/calls/{id}
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x', {
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/calls/cal_8Jq2mT4x",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"account_id": "acc_ridgeline",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "completed",
"legs": [
{
"id": "leg_a1",
"to": "+15125550147",
"result": "bridged",
"rang_ms": 4120
},
{
"id": "leg_a2",
"to": "+15125550163",
"result": "canceled",
"rang_ms": 15400
},
{
"id": "leg_a3",
"to": "+15125550108",
"result": "machine_voicemail",
"rang_ms": 9800
}
],
"amd_result": "human",
"speech_ended_to_bridge_ms": 980,
"ivr_path": null,
"hold_seconds": null,
"answered_at": "2026-11-03T15:42:03.542Z",
"bridged_at": "2026-11-03T15:42:07.412Z",
"ended_at": "2026-11-03T15:46:29.903Z",
"hangup_by": "prospect",
"billed_seconds": 264,
"disposition": "callback_requested",
"recording_id": "rec_0Hy3k",
"rate_per_minute": "0.12",
"amount": "0.528",
"charges": [
{
"item": "outbound_minutes",
"quantity": 4.4,
"amount": "0.528"
}
],
"surcharges": [],
"total": "0.528"
} List call records
Returns finished calls, newest first. Filter by dial session, agent, lead or date range.
GET/v1/call_records
| Query parameters | Type | Description |
|---|---|---|
dial_session_id | string | Calls placed by this session. |
agent_id | string | Calls taken by this agent. |
lead_id | string | Calls to this lead. |
created_after | string | ISO 8601. Calls started at or after this time. |
created_before | string | ISO 8601. Calls started before this time. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/call_records?dial_session_id=dls_31Ka9&limit=50" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/call_records?dial_session_id=dls_31Ka9&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/call_records",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"dial_session_id": "dls_31Ka9",
"limit": 50,
},
).json() {
"object": "list",
"data": [
{
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"to": "+15125550147",
"agent_id": "agt_114",
"status": "completed",
"billed_seconds": 264,
"disposition": "callback_requested",
"ended_at": "2026-11-03T15:46:29.903Z"
},
{
"id": "cal_7Hd1aP2c",
"object": "call",
"dial_session_id": "dls_31Ka9",
"to": "+15125550185",
"agent_id": "agt_114",
"status": "completed",
"billed_seconds": 71,
"disposition": "not_interested",
"ended_at": "2026-11-03T15:39:12.660Z"
}
],
"has_more": true
} Hold a call
The prospect hears hold music. The agent can talk to others. Release with unhold.
POST/v1/calls/{id}/hold
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hold \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hold', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hold",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": true,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z"
} Take a call off hold
Reconnects the prospect and the agent.
POST/v1/calls/{id}/unhold
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unhold \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unhold', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unhold",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": false,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z"
} Mute the agent
Mutes the agent toward the prospect. The agent still hears the prospect.
POST/v1/calls/{id}/mute
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/mute \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/mute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/mute",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": false,
"muted": true,
"bridged_at": "2026-11-03T15:42:07.412Z"
} Unmute the agent
The prospect hears the agent again.
POST/v1/calls/{id}/unmute
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unmute \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unmute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/unmute",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": false,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z"
} Send digits
Sends key presses on the prospect's line, for example to reach an extension.
POST/v1/calls/{id}/dtmf
| Body parameters | Type | Description |
|---|---|---|
digitsrequired | string | 0 to 9, * and #, up to 32 characters. Each w adds a half-second pause. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/dtmf \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"digits": "3w2#"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/dtmf', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
digits: "3w2#"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/dtmf",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"digits": "3w2#",
},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": false,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z",
"dtmf_sent": "3w2#"
} Transfer a call
A warm transfer keeps the first agent on the line while the target joins, then the first agent leaves with transfer/complete. A cold transfer hands the call over and frees the first agent right away. call.transferred reports each result: joined, completed, no_answer or canceled.
POST/v1/calls/{id}/transfer
| Body parameters | Type | Description |
|---|---|---|
moderequired | warm | cold | warm keeps the first agent on the call until they leave. cold hands the call over right away. |
torequired | string | agent:agt_..., pool:pool_... or a phone number in E.164. |
consult | boolean | Warm only. The prospect is on hold while the two agents talk, and joins when the transfer completes. Default false. |
notes | string | Shown to the receiving agent with the lead details. |
timeout_seconds | integer | How long the target rings. 10 to 60, default 30. |
on_no_answer | return_to_agent | voicemail | hangup | What happens when the target does not answer. Default return_to_agent. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "warm",
"to": "agent:agt_closer_09",
"notes": "Wants a roof inspection quote this week.",
"timeout_seconds": 30,
"on_no_answer": "return_to_agent"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
mode: "warm",
to: "agent:agt_closer_09",
notes: "Wants a roof inspection quote this week.",
timeout_seconds: 30,
on_no_answer: "return_to_agent"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"mode": "warm",
"to": "agent:agt_closer_09",
"notes": "Wants a roof inspection quote this week.",
"timeout_seconds": 30,
"on_no_answer": "return_to_agent",
},
).json() {
"id": "trf_6Wd20Hs",
"object": "transfer",
"call_id": "cal_8Jq2mT4x",
"mode": "warm",
"from_agent_id": "agt_114",
"to": "agent:agt_closer_09",
"consult": false,
"status": "ringing",
"timeout_seconds": 30,
"on_no_answer": "return_to_agent",
"created_at": "2026-11-03T15:44:51.300Z"
} Complete a warm transfer
The first agent leaves the call. The target has the call and the first agent is free for the next call. call.transferred arrives with result completed.
POST/v1/calls/{id}/transfer/complete
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/complete \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/complete', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/complete",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "trf_6Wd20Hs",
"object": "transfer",
"call_id": "cal_8Jq2mT4x",
"mode": "warm",
"from_agent_id": "agt_114",
"to": "agent:agt_closer_09",
"status": "completed"
} Cancel a transfer
Stops a transfer before it completes. The target is dropped and the call stays with the first agent. call.transferred arrives with result canceled.
POST/v1/calls/{id}/transfer/cancel
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/cancel \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transfer/cancel",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "trf_6Wd20Hs",
"object": "transfer",
"call_id": "cal_8Jq2mT4x",
"mode": "warm",
"from_agent_id": "agt_114",
"to": "agent:agt_closer_09",
"status": "canceled"
} Add people to a call
Adds up to 5 people to a live call: agents or outside phone numbers. A participant with mode listen hears everyone and is not heard, for supervisors. Each person gets a participant ID. Joins and departures send call.participant_joined and call.participant_left.
POST/v1/calls/{id}/conference
| Body parameters | Type | Description |
|---|---|---|
participantsrequired | array | Up to 5 people. Each has to (agent:agt_... or a phone number in E.164) and mode. |
participants[].mode | talk | listen | listen joins without being heard. Default talk. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/conference \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"participants": [
{
"to": "agent:agt_closer_09",
"mode": "talk"
},
{
"to": "agent:agt_sup_02",
"mode": "listen"
}
]
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/conference', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
participants: [
{
to: "agent:agt_closer_09",
mode: "talk"
},
{
to: "agent:agt_sup_02",
mode: "listen"
}
]
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/conference",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"participants": [
{
"to": "agent:agt_closer_09",
"mode": "talk",
},
{
"to": "agent:agt_sup_02",
"mode": "listen",
},
],
},
).json() {
"call_id": "cal_8Jq2mT4x",
"participants": [
{
"id": "par_1",
"role": "prospect",
"to": "+15125550147",
"mode": "talk",
"status": "connected"
},
{
"id": "par_2",
"role": "agent",
"agent_id": "agt_114",
"mode": "talk",
"status": "connected"
},
{
"id": "par_3",
"role": "agent",
"agent_id": "agt_closer_09",
"mode": "talk",
"status": "ringing"
},
{
"id": "par_4",
"role": "agent",
"agent_id": "agt_sup_02",
"mode": "listen",
"status": "ringing"
}
]
} Remove a participant
Removes one person from the call. The call goes on for everyone else. When the last agent leaves, the call ends.
DELETE/v1/calls/{id}/participants/{participant_id}
curl -X DELETE https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/participants/par_4 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/participants/par_4', {
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/calls/cal_8Jq2mT4x/participants/par_4",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "par_4",
"object": "participant",
"call_id": "cal_8Jq2mT4x",
"status": "left"
} Forward a call
Moves the prospect to another phone number and frees the agent at once, with no announcement. If the number does not answer within timeout_seconds, the call ends. call.transferred arrives with mode forward.
POST/v1/calls/{id}/forward
| Body parameters | Type | Description |
|---|---|---|
torequired | string | The number to forward to, E.164. |
timeout_seconds | integer | How long the number rings. 10 to 60, default 30. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/forward \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15125550108",
"timeout_seconds": 30
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/forward', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: "+15125550108",
timeout_seconds: 30
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/forward",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"to": "+15125550108",
"timeout_seconds": 30,
},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": null,
"lead_id": "led_44Hc2",
"status": "in_progress",
"on_hold": false,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z",
"forwarded_to": "+15125550108"
} Hang up a call
Ends the call for everyone. call.completed follows.
POST/v1/calls/{id}/hangup
curl -X POST https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hangup \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hangup', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/hangup",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"dial_session_id": "dls_31Ka9",
"direction": "outbound",
"from": "+15125550199",
"to": "+15125550147",
"agent_id": "agt_114",
"lead_id": "led_44Hc2",
"status": "completed",
"on_hold": false,
"muted": false,
"bridged_at": "2026-11-03T15:42:07.412Z",
"hangup_by": "agent",
"ended_at": "2026-11-03T15:46:29.903Z"
} Save a disposition
Saves the outcome of a call. With callback_at we call the lead back at that time with the same agent, or with the pool if that agent is busy or offline. A callback time outside the lead's calling hours moves to the next open window.
POST/v1/calls/{id}/disposition
| Body parameters | Type | Description |
|---|---|---|
coderequired | string | Your own outcome code, for example callback_requested. |
notes | string | Free text for the next agent. |
callback_at | string | When to call back, ISO 8601 with offset. Sends callback.scheduled. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "callback_requested",
"notes": "Owner on a job. Call Thursday after 2 pm.",
"callback_at": "2026-11-06T14:00:00-06:00"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: "callback_requested",
notes: "Owner on a job. Call Thursday after 2 pm.",
callback_at: "2026-11-06T14:00:00-06:00"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/disposition",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"code": "callback_requested",
"notes": "Owner on a job. Call Thursday after 2 pm.",
"callback_at": "2026-11-06T14:00:00-06:00",
},
).json() {
"id": "cal_8Jq2mT4x",
"object": "call",
"lead_id": "led_44Hc2",
"status": "completed",
"disposition": "callback_requested",
"notes": "Owner on a job. Call Thursday after 2 pm.",
"callback_at": "2026-11-06T14:00:00-06:00"
} Start a stream
Sends live call audio or the live transcript to your WebSocket server. Audio is 16 kHz, 16-bit mono PCM in base64 JSON messages, with agent and prospect on separate tracks. Transcript messages need transcription=live on the call and carry event, speaker, turn_id, start_ms, end_ms, text, is_final and sequence. The stream closes when the call ends.
POST/v1/calls/{id}/streams
| Body parameters | Type | Description |
|---|---|---|
urlrequired | string | Your WebSocket server, wss:// only. |
content | audio | transcript | transcript sends transcript messages instead of audio. Default audio. |
tracks | agent | prospect | both | Which speakers to send. Default both. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "wss://coach.example.com/audio",
"content": "audio",
"tracks": "both"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: "wss://coach.example.com/audio",
content: "audio",
tracks: "both"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"url": "wss://coach.example.com/audio",
"content": "audio",
"tracks": "both",
},
).json() {
"id": "str_1Pv77Bc",
"object": "stream",
"call_id": "cal_8Jq2mT4x",
"url": "wss://coach.example.com/audio",
"content": "audio",
"tracks": "both",
"status": "connecting",
"created_at": "2026-11-03T15:42:08.010Z"
} Stop a media stream
Closes the stream before the call ends. The call is not affected.
DELETE/v1/calls/{id}/streams/{stream_id}
curl -X DELETE https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams/str_1Pv77Bc \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams/str_1Pv77Bc', {
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/calls/cal_8Jq2mT4x/streams/str_1Pv77Bc",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "str_1Pv77Bc",
"object": "stream",
"call_id": "cal_8Jq2mT4x",
"status": "closed"
} List scheduled callbacks
Returns scheduled callbacks, soonest first. A callback is set by a disposition with callback_at or by a voicemail, and sends callback.scheduled. At callback_at the lead is called by the same agent, or by the agent pool when that agent is busy or offline.
GET/v1/callbacks
| Query parameters | Type | Description |
|---|---|---|
agent_id | string | Callbacks for this agent. |
lead_id | string | Callbacks for this lead. |
status | string | scheduled, completed or canceled. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/callbacks?agent_id=agt_114&status=scheduled" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/callbacks?agent_id=agt_114&status=scheduled', {
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/callbacks",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"agent_id": "agt_114",
"status": "scheduled",
},
).json() {
"object": "list",
"data": [
{
"id": "cbk_2Rt81Xn",
"object": "callback",
"lead_id": "led_44Hc2",
"agent_id": "agt_114",
"callback_at": "2026-11-06T20:00:00Z",
"source": "disposition",
"call_id": "cal_8Jq2mT4x",
"status": "scheduled"
}
],
"has_more": false
} Cancel a callback
Cancels a scheduled callback. The lead stays in its list.
DELETE/v1/callbacks/{id}
curl -X DELETE https://api.liveleg.dev/v1/callbacks/cbk_2Rt81Xn \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/callbacks/cbk_2Rt81Xn', {
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/callbacks/cbk_2Rt81Xn",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "cbk_2Rt81Xn",
"object": "callback",
"lead_id": "led_44Hc2",
"agent_id": "agt_114",
"callback_at": "2026-11-06T20:00:00Z",
"source": "disposition",
"call_id": "cal_8Jq2mT4x",
"status": "canceled"
}