Agent calling

We host the agent's side of the call. Agents talk from the browser or a SIP phone, stay connected for the whole dial session, and each live person is bridged into that open line.

Agents and pools

An agent is one person who takes calls. Create one per seat and put agents in pools when they share calls from one dial session. A disconnected agent gets no calls. Their session pauses until they reconnect. agent.connected and agent.disconnected report each change on your webhook.

POST/v1/agents

ParametersTypeDescription
namerequired string Shown in the dashboard and on call records.
agent_pool_id string The pool the agent shares calls in. Created if it does not exist.
sip boolean Give the agent a SIP address and credentials. Default false.
email string The agent's work email.
metadata object Up to 20 keys. Returned on events about the agent.
POST/v1/agents
curl https://api.liveleg.dev/v1/agents \
  -H "Authorization: Bearer $LIVELEG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jordan Pike",
    "agent_pool_id": "pool_closers",
    "sip": false
  }'

Tokens

The browser and the mobile libraries connect with a short-lived token for one agent. Mint it on your server with your API key and pass it to the page. A token lasts up to 1 hour and works for that agent only. It is checked when the connection opens, and an open connection stays up for the whole shift. Reconnecting needs a new token.

POST/v1/agents/{id}/tokens

POST/v1/agents/agt_114/tokens
curl https://api.liveleg.dev/v1/agents/agt_114/tokens \
  -H "Authorization: Bearer $LIVELEG_API_KEY" \
  -d ttl_seconds=3600
Response
{
  "token": "eyJhbGciOiJFUzI1NiJ9.eyJhZ3QiOiJhZ3RfMTE0In0.Xq9...",
  "agent_id": "agt_114",
  "expires_at": "2026-11-03T15:00:02Z"
}

Browser

Install @liveleg/voice from npm and call Agent.connect(token, options). It checks the microphone and network, then opens one audio connection over WebRTC that stays open between calls. Bridged calls arrive on that connection. The agent does not press answer and hears no ring. Time spent connected and waiting is never billed.

Browser
import { Agent } from '@liveleg/voice';

// Your server mints the token. Never put an API key in the browser.
const getToken = () => fetch('/api/liveleg-token').then((r) => r.json()).then((d) => d.token);

const agent = await Agent.connect(await getToken(), {
  getToken,
  inputDeviceId: savedMicId,
});

agent.on('call.bridged', (call) => {
  showLead(call.leadId, call.to);
});
agent.on('call.ended', (call) => openWrapUp(call.id));
agent.on('network.quality', ({ level }) => setSignal(level));
agent.on('disconnected', ({ reason }) => showOffline(reason));

document.querySelector('#mute').onclick = () => agent.mute();
document.querySelector('#hold').onclick = () => agent.hold();
document.querySelector('#hangup').onclick = () => agent.hangup();

Everything the SDK does on a call is also available on the Calls API. Your server can act on the same call at any time.

Connect options

Agent.connect returns a promise for the connected agent. It rejects with a code when the agent cannot connect.

OptionTypeDescription
getToken function Returns a promise for a fresh token. Called when the SDK has to reconnect.
inputDeviceId string Microphone to use. Default is the system microphone.
outputDeviceId string Speaker or headset to use. Default is the system output.
autoReconnect boolean Reconnect after a network drop. A bridged call stays up for 30 seconds while the SDK reconnects. Default true.
echoCancellation boolean Default true.
noiseSuppression boolean Default true.
logLevel error | warn | info | debug Console logging. Default warn.
Browser
try {
  await Agent.connect(token);
} catch (err) {
  // err.code: microphone_denied, no_microphone, invalid_token or network_blocked
  showSetupHelp(err.code);
}

SDK events

Listen with agent.on(event, handler) and stop with agent.off(event, handler). These events go to the page only. Your server gets the matching webhooks, such as call.bridged and agent.disconnected, from Webhooks and events.

EventSent whenPayload
call.bridgedA live person is connected. The agent hears them right away.id, leadId, externalId, to, from, dialSessionId, ivrPath, holdSeconds
call.endedThe bridged call ends.id, hangupBy, durationSeconds
session.pausedThe agent's dial session is paused.dialSessionId, reason
session.resumedThe dial session starts dialing again.dialSessionId
device.changedA microphone or speaker is plugged in, removed or selected.kind (input, output), deviceId, label
device.errorThe microphone is blocked or lost.code (microphone_denied, device_lost), message
network.qualityEvery 5 seconds during a call.level (good, fair, poor), rttMs, jitterMs, packetLoss
network.reconnectingThe connection dropped and the SDK is reconnecting.attempt
network.reconnectedThe connection is back.
disconnectedThe connection closed. The dial session pauses.reason (disconnect, network, token_expired, replaced)

SDK methods

Call methods act on the current call. agent.call holds the current call, or null between calls. agent.muted and agent.onHold hold the current state.

MethodWhat it does
mute()Mutes the agent's microphone toward the lead.
unmute()Turns the microphone back on.
hold()The lead hears hold music. Same as hold on the Calls API.
resume()Takes the lead off hold. Same as unhold on the Calls API.
sendDigits(digits)Sends DTMF on the lead's line. Digits 0 to 9, * and #, up to 32 characters. Each w adds a half-second pause.
transfer({ to, mode, timeoutSeconds })Warm or cold transfer to agent:, pool: or a phone number. Same as the Calls API transfer.
hangup()Ends the current call. In a dial session, the next round starts.
setInputDevice(deviceId)Switches the microphone, also during a call.
setOutputDevice(deviceId)Switches the speaker or headset.
disconnect()Closes the connection. The dial session pauses until the agent connects again.

Mobile apps

Libraries for iOS and Android are available on request. They use the same tokens, events and methods as the browser SDK.

  • During a dial session the app keeps the connection open in the foreground, the same as the browser.
  • The library registers the device for push notifications. When a callback or a transfer rings an agent whose app is in the background, a push wakes the app and it shows the phone's own incoming call screen.
  • Bluetooth headsets and switching between speaker and earpiece are handled by the library.

SIP phones

Create the agent with sip=true to get a SIP address and credentials. Each agent has its own address. Register a desk phone, a softphone or your phone system to it. The password is shown once.

Response
{
  "id": "agt_114",
  "sip": {
    "address": "sip:agt_114@sip.liveleg.dev",
    "transport": "tls",
    "port": 5061,
    "media": "srtp",
    "codecs": ["opus", "g722", "pcmu", "pcma"],
    "username": "agt_114",
    "password": "shown once"
  }
}
  • Signaling over TLS on port 5061 and media over SRTP. Unencrypted connections are refused.
  • Codecs: Opus, G.722 and G.711 (PCMU and PCMA).
  • One active registration per agent. A new registration replaces the old one and sends agent.disconnected with reason replaced.
  • When a dial session starts, we call the agent's SIP address once. The agent answers and that call stays open for the whole session. Each live person is bridged into it with no ring. Hanging up the phone pauses the session.
  • Single calls and callbacks to an agent who is not in a session ring the phone as a normal call.
  • Keys pressed on the phone are sent to the lead's line. Transfers and dispositions go through the Calls API.

Create new credentials at any time. The old password stops working right away.

POST/v1/agents/{id}/sip_credentials

curl POST/v1/agents/agt_114/sip_credentials
curl -X POST https://api.liveleg.dev/v1/agents/agt_114/sip_credentials \
  -H "Authorization: Bearer $LIVELEG_API_KEY"

Supervisor listening

A supervisor listens to a live call without being heard by the agent or the lead. The supervisor is an agent connected in the browser or on a SIP phone, added to the call as a participant with mode=listen. Remove them with DELETE /v1/calls/{id}/participants/{participant_id}, or listening ends when the call ends. Details are in Add and remove people.

POST/v1/calls/{id}/conference

POST/v1/calls/cal_8Jq2mT4x/conference
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_201",
        "mode": "listen"
      }
    ]
  }'

Media streams

Send live call audio to your own WebSocket server, for coaching tools, live notes or your own speech models. Audio is 16 kHz, 16-bit mono PCM, base64 in JSON messages, with agent and prospect on separate tracks. Choose agent, prospect or both in tracks. Set content=transcript to receive the live transcript on the same endpoint instead of audio. The stream closes when the call ends.

POST/v1/calls/{id}/streams

POST/v1/calls/cal_8Jq2mT4x/streams
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams \
  -H "Authorization: Bearer $LIVELEG_API_KEY" \
  -d url=wss://coach.example.com/audio \
  -d tracks=both
WebSocket message
{
  "event": "media",
  "call_id": "cal_8Jq2mT4x",
  "track": "prospect",
  "sequence": 412,
  "timestamp_ms": 8240,
  "payload": "AAD+/wIA..."
}