Errors
Every failed request returns an HTTP status code and a JSON body with an error object. The code field tells your app what happened and what to do next.
The error object
Fields that do not apply to an error are left out.
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable. One of the codes below. Your code should branch on this field. |
message | string | A sentence for people. The wording can change. |
param | string | The parameter at fault, for invalid_request. |
reason | string | The pre-dial check that failed, for blocked_by_rule. The same values as on call.blocked. |
rule | string | The exact rule, with the state where it applies, for blocked_by_rule. |
next_allowed_at | string | When the same request can pass, for rules that open again, such as calling hours. |
capability | string | The feature that is not active yet, for requirement_missing. |
requirements | string[] | The items still due for that feature, for requirement_missing. |
request_id | string | Our ID for the request. Quote it when you contact us. |
curl https://api.liveleg.dev/v1/calls \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-d to=+12155550173 \
-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',
},
body: JSON.stringify({
to: '+12155550173',
caller_id_pool: 'pool_local_presence',
agent_id: 'agt_114',
}),
});
const body = await res.json();
if (!res.ok) {
const { code, rule, next_allowed_at } = body.error;
if (code === 'blocked_by_rule') scheduleRetry(next_allowed_at);
}import os, requests
res = requests.post(
"https://api.liveleg.dev/v1/calls",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"to": "+12155550173",
"caller_id_pool": "pool_local_presence",
"agent_id": "agt_114",
},
)
body = res.json()
if not res.ok:
error = body["error"]
if error["code"] == "blocked_by_rule":
schedule_retry(error.get("next_allowed_at")) {
"error": {
"code": "blocked_by_rule",
"message": "The lead's local time is outside calling hours.",
"rule": "calling_hours.PA",
"next_allowed_at": "2026-11-03T14:00:00Z",
"request_id": "req_6tB02xQe"
}
} HTTP status codes
| Status | Meaning |
|---|---|
| 200, 201 | The request worked. 201 when an object was created. |
| 400 | The request is malformed or a parameter is wrong. |
| 401 | The API key is missing, wrong or revoked. |
| 403 | The key or the account is not allowed to do this. |
| 404 | The object does not exist in this account and mode. |
| 409 | The request conflicts with an earlier request or with the state of the object. |
| 422 | The request is valid and a compliance check stopped it. |
| 500, 503 | Our side failed. Safe to retry with the same Idempotency-Key. |
Error codes
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | A parameter is missing or has the wrong format. param names it. | Fix the parameter. Do not retry the same request. |
| 401 | invalid_api_key | The key is missing, wrong or revoked. | Check the Authorization header and that the key is still active. Test keys start with ll_test_ and live keys with ll_live_. |
| 403 | insufficient_scope | The key is valid but its scopes do not allow the request. | Use a key with the scope, or add the scope to a new key. |
| 403 | requirement_missing | The account or subaccount has items due before this feature turns on. | Send the items in requirements, or send your client the onboarding link. See below. |
| 404 | not_found | The object does not exist in this account. | Check the ID, the Liveleg-Account header and the mode. Test objects are not visible to live keys. |
| 409 | idempotency_conflict | The Idempotency-Key was used with a different request body. | Use a new key for a new request. Reuse a key only to retry the same request. |
| 409 | invalid_state | The object is in a state that does not allow the action, such as resuming a stopped dial session. | Retrieve the object, check status and start a new one if needed. |
| 422 | blocked_by_rule | A compliance check stopped the request. rule names the check. | Do not override it. Wait for next_allowed_at when it is set, or fix the lead data. |
| 500, 503 | server_error | Our side failed. | Retry with the same Idempotency-Key. Nothing runs twice. |
We add new codes within v1. Treat a code you do not know by its HTTP status.
Blocked by a rule
A request that fails a compliance check returns 422 with blocked_by_rule. When a dial session skips a lead, no request fails. The session sends a call.blocked event with the same reason and rule. The rule names are below, and each reason is explained in Pre-dial checks. A state code replaces <state>, for example calling_hours.PA.
| Rule | Meaning | What to do |
|---|---|---|
calling_hours.<state> | The lead's local time is outside the calling hours for their state. | next_allowed_at is the next open time. |
call_limit.<state> | The lead reached the state limit, for example 3 sales calls in 24 hours in Florida, Oklahoma and Maryland. Calls from all your numbers count. | next_allowed_at is when the oldest call leaves the window. |
dnc.internal | The number is on your do-not-call list. | Remove the lead from the list. |
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, or remove the lead. |
dnc.state.<state> | The number is on a state do-not-call list you hold. | Attach a consent record that covers the call, or remove the lead. |
consent.required | require_consent is on and the lead has no consent record. | Store the consent with POST /v1/consents and send consent_id on the lead. |
lead.state | The lead has no state and the area code covers more than one state. | Send state on the lead. |
lookup.line_status | The lookup before the dial found the number disconnected. | Remove the lead or update the number. |
messaging.opt_out | The recipient opted out of messages with STOP or another opt-out word. | Send nothing until they reply START or UNSTOP. |
The full rules are in Compliance.
Missing requirements
Each feature of an account or subaccount turns on when its checks pass. A request that needs a feature that is not active returns 403 with requirement_missing. capability names the feature and requirements lists what is still due. The same list is on GET /v1/accounts/{id}/requirements. See Subaccounts.
{
"error": {
"code": "requirement_missing",
"message": "Outbound calls to consumers are not active for this account yet.",
"capability": "outbound_consumer",
"requirements": ["documents.consent_form", "documents.script"],
"request_id": "req_9Lm31vKa"
}
} Retries
- Retry 500, 503 and network timeouts. Send the same
Idempotency-Key. A request that already ran returns its first response and places nothing twice. - Wait before each retry and double the wait each time, starting at 1 second.
- Do not retry 400, 401, 403, 404 or 409 without changing the request.
- Retry a 422 only after
next_allowed_at, when it is set.
See Idempotency.