API basics
The Liveleg API is a REST API at https://api.liveleg.dev/v1. Send JSON or form-encoded bodies and get JSON back. This page covers what every endpoint has in common.
Requests and responses
- Authenticate with a Bearer API key. See Authentication.
- IDs carry a prefix for their type:
cal_calls,dls_dial sessions,num_numbers,agt_agents. - Phone numbers are E.164, such as
+15125550147. Times are ISO 8601 in UTC. Durations end in_msor_seconds. - Amounts are strings in US dollars, such as
"12.40".
Errors
Errors use standard HTTP status codes. The body has a machine-readable code and a message for people. When a compliance check stops a request, rule names the check and next_allowed_at says when the lead can be called. Quote request_id when you contact us.
{
"error": {
"code": "blocked_by_rule",
"message": "The lead's local time is outside calling hours.",
"reason": "outside_calling_hours",
"rule": "calling_hours.PA",
"next_allowed_at": "2026-11-03T14:00:00Z",
"request_id": "req_6tB02xQe"
}
} | Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or has the wrong format. param names it. |
| 401 | invalid_api_key | The key is missing, wrong or revoked. |
| 403 | insufficient_scope | The key is valid but its scopes do not allow the request. |
| 404 | not_found | The object does not exist in this account. |
| 409 | idempotency_conflict | The Idempotency-Key was used with a different request body. |
| 422 | blocked_by_rule | A compliance check stopped the request. rule names the check. |
| 500, 503 | server_error | Our side failed. Retry with the same Idempotency-Key. |
Idempotency
Send an Idempotency-Key header on any POST. If you retry with the same key within 24 hours, you get the first response back and nothing runs twice. A retry after a timeout with the same key does not place a second call. Use a value tied to the action, such as your CRM ID plus an attempt number. The same key with a different body returns 409.
curl https://api.liveleg.dev/v1/calls \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Idempotency-Key: crm-88213-call-3" \
-d to=+15125550147 \
-d caller_id_pool=pool_local_presence \
-d agent_id=agt_114const res = await fetch('https://api.liveleg.dev/v1/calls', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'crm-88213-call-3',
},
body: JSON.stringify({
to: '+15125550147',
caller_id_pool: 'pool_local_presence',
agent_id: 'agt_114',
}),
});
const call = await res.json();import os, requests
call = requests.post(
"https://api.liveleg.dev/v1/calls",
headers={
"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}",
"Idempotency-Key": "crm-88213-call-3",
},
json={
"to": "+15125550147",
"caller_id_pool": "pool_local_presence",
"agent_id": "agt_114",
},
).json() Pagination
List endpoints return the newest objects first. Set limit from 1 to 100 (default 20). To get the next page, pass the last ID you received as starting_after. Stop when has_more is false.
curl "https://api.liveleg.dev/v1/call_records?limit=100&starting_after=cal_8Jq2mT4x" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/call_records?limit=100&starting_after=cal_8Jq2mT4x', {
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const page = await res.json();import os, requests
page = requests.get(
"https://api.liveleg.dev/v1/call_records",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={"limit": 100, "starting_after": "cal_8Jq2mT4x"},
).json() {
"object": "list",
"data": [
{"id": "cal_7Hd1aP2c", "object": "call", "status": "completed"}
],
"has_more": true
} Versioning
The version is in the path. Within v1 we add new endpoints, fields and event types without notice. Your code should ignore fields it does not know. Removing or renaming anything needs a new version, and the old one keeps working for at least 12 months after the new one ships.
Test mode
Requests made with a test key run the same validation and compliance checks as live ones, and place no calls. Dial sessions simulate answers, machines and no-answers and send the same events, with livemode false. Test objects are kept apart from live ones and cost nothing.