Libraries and SDKs
The Liveleg API is plain HTTPS with JSON bodies. Any language with an HTTP client can call it. This page has a short helper for Node.js and Python, and the browser library agents use to take calls.
Plain HTTPS and JSON
- Base URL
https://api.liveleg.dev/v1. - Send the API key as
Authorization: Bearer. See Authentication. - Send bodies as JSON or form-encoded. Responses are JSON.
- Errors return an
errorobject with acode. See Errors. - Lists page with
limit,starting_afterandhas_more. See Pagination.
We do not publish server libraries. The helpers below cover authentication, idempotency keys, acting as a subaccount, errors and pagination. Copy them into your project and change them as you need.
curl
Every example in these docs has a curl tab. Export your key as LIVELEG_API_KEY first.
# JSON body
curl https://api.liveleg.dev/v1/dial_sessions \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: agent-114-morning" \
-d '{"agent_id": "agt_114", "lead_list_id": "lst_roofing_tx", "lines": "auto"}'
# Form-encoded body, same request
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 Node.js
Node.js 18 and later have fetch built in. The helper needs no packages.
// liveleg.js
const BASE = 'https://api.liveleg.dev/v1';
export class LivelegError extends Error {
constructor(status, error) {
super(error.message);
this.status = status;
this.code = error.code;
this.error = error;
}
}
export async function liveleg(method, path, { body, query, idempotencyKey, account } = {}) {
const url = new URL(BASE + path);
for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, String(v));
const headers = { Authorization: `Bearer ${process.env.LIVELEG_API_KEY}` };
if (body) headers['Content-Type'] = 'application/json';
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
if (account) headers['Liveleg-Account'] = account;
const res = await fetch(url, { method, headers, body: body && JSON.stringify(body) });
const data = await res.json();
if (!res.ok) throw new LivelegError(res.status, data.error);
return data;
}
export async function* listAll(path, query = {}) {
let after;
do {
const page = await liveleg('GET', path, { query: { ...query, limit: 100, ...(after && { starting_after: after }) } });
yield* page.data;
after = page.has_more ? page.data.at(-1).id : undefined;
} while (after);
} import { liveleg, listAll, LivelegError } from './liveleg.js';
const session = await liveleg('POST', '/dial_sessions', {
body: { agent_id: 'agt_114', lead_list_id: 'lst_roofing_tx', lines: 'auto' },
idempotencyKey: 'agent-114-morning',
});
for await (const call of listAll('/call_records', { dial_session_id: session.id })) {
console.log(call.id, call.billed_seconds, call.amount);
}
try {
await liveleg('POST', '/calls', { body: { to: '+12155550173', caller_id_pool: 'pool_local_presence', agent_id: 'agt_114' } });
} catch (err) {
if (err instanceof LivelegError && err.code === 'blocked_by_rule') console.log(err.error.next_allowed_at);
else throw err;
} Python
The helper uses requests.
# liveleg.py
import os, requests
BASE = "https://api.liveleg.dev/v1"
http = requests.Session()
http.headers["Authorization"] = f"Bearer {os.environ['LIVELEG_API_KEY']}"
class LivelegError(Exception):
def __init__(self, status, error):
super().__init__(error["message"])
self.status = status
self.code = error["code"]
self.error = error
def liveleg(method, path, body=None, query=None, idempotency_key=None, account=None):
headers = {}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
if account:
headers["Liveleg-Account"] = account
res = http.request(method, BASE + path, json=body, params=query, headers=headers)
data = res.json()
if not res.ok:
raise LivelegError(res.status_code, data["error"])
return data
def list_all(path, query=None):
query = {**(query or {}), "limit": 100}
while True:
page = liveleg("GET", path, query=query)
yield from page["data"]
if not page["has_more"]:
return
query["starting_after"] = page["data"][-1]["id"] from liveleg import liveleg, list_all, LivelegError
session = liveleg(
"POST",
"/dial_sessions",
body={"agent_id": "agt_114", "lead_list_id": "lst_roofing_tx", "lines": "auto"},
idempotency_key="agent-114-morning",
)
for call in list_all("/call_records", {"dial_session_id": session["id"]}):
print(call["id"], call["billed_seconds"], call["amount"])
try:
liveleg("POST", "/calls", body={"to": "+12155550173", "caller_id_pool": "pool_local_presence", "agent_id": "agt_114"})
except LivelegError as err:
if err.code != "blocked_by_rule":
raise
print(err.error.get("next_allowed_at")) Browser library for agents
@liveleg/voice connects an agent's browser to Liveleg over WebRTC. It runs in the page and uses a short-lived agent token from your server. It never sees your API key.
npm install @liveleg/voice import { Agent } from '@liveleg/voice';
// Your server mints the token with POST /v1/agents/{id}/tokens.
const { token } = await fetch('/api/liveleg-token').then((r) => r.json());
const agent = await Agent.connect(token);
agent.on('call.bridged', (call) => showLead(call.leadId, call.to));
agent.on('call.ended', (call) => openWrapUp(call.id));
agent.on('session.paused', () => showPaused());
document.querySelector('#mute').onclick = () => agent.mute();
document.querySelector('#hold').onclick = () => agent.hold();
document.querySelector('#hangup').onclick = () => agent.hangup(); It sends call.bridged, call.ended and session.paused to the page, and exposes mute, hold, dtmf, transfer and hangup. See Agent calling.
Webhook signatures
Deliveries follow the Standard Webhooks spec. Verify them with the code in Verify signatures, or with any Standard Webhooks library, such as standardwebhooks on npm and PyPI.