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 error object with a code. See Errors.
  • Lists page with limit, starting_after and has_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.

curl POST/v1/dial_sessions
# 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
// 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);
}
Node.js
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
# 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"]
Python
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.

Shell
npm install @liveleg/voice
Browser
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.