API Docs
Getting started

Introduction

The BelSmart API lets you build calling workflows on top of the same Tier 1 telecom network that powers the BelSmart dialer — trigger calls, read agent status, pull call records, and fetch recordings.

Partial draft. Call APIs, Agent List, Call Detail Records and Recording reflect the actual BelSmart SDK. The base URL and quick-start example on this page are still illustrative — confirm with engineering before publishing.

Base URL

All API requests are made against a single base URL:

Base URL
https://api.belsmart.io/v1

Quick start

Every request needs your API key in the Authorization header. Here's a call to list your agents:

cURL
curl https://api.belsmart.io/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY"

Response format

All responses are JSON. List endpoints return a data array plus pagination fields; single-resource endpoints return the object directly. Errors return a 4xx/5xx status with a machine-readable code and a human-readable message.

Getting started

Authentication

Requests are authenticated with an API key, sent as a bearer token. Generate keys from your BelSmart dashboard under Settings → API Keys.

Using your API key

Add the key to the Authorization header of every request:

cURL
curl https://api.belsmart.io/v1/calls \
  -H "Authorization: Bearer YOUR_API_KEY"
🔒 Keep your API key secret. Don't commit it to source control or expose it in client-side code. Rotate a key immediately if it's ever exposed.

Key types

Prefix Environment Notes
sk_live_ Production Real calls, billed to your account.
sk_test_ Sandbox Simulated call flow, no telecom charges.
API reference

Calls API — how it's used

This page walks through how a CRM actually uses the Calls API day to day — placing a call, reacting to what happens next, and controlling it while it's live.

Before you start: everything below assumes setup is already done — your tenant has issued an API key/secret pair, and you've mapped the signed-in CRM user to a BelSmart agentUuid.

What you can do with it

Place a call To a contact's number with one function call.
React to the lifecycle Ringing, answered, ended — via events, not polling.
Control a live call Mute, hold, send DTMF, adjust volume.
Bring in a third party Supervisor listen-ins, warm intros, ad-hoc conferences.
Hand off a call Attended transfer — without ending it for the customer.
Record what happened Attach a disposition once the call ends.
1

Connecting before you can call anything

A call can only be placed once the client has connected and registered the agent's line:

TypeScript
const client = new BelsmartCrmClient({
  apiBaseUrl: "https://pbx.example.com",
  apiKey: "YOUR_API_KEY",
  apiSecret: "YOUR_API_SECRET",
  agentUuid: "<mapped agent uuid>",
});

await client.init();

client.call() won't attempt to dial if the client isn't connected — it rejects immediately instead. In practice, your CRM's call button should stay disabled until the client reports it's ready:

TypeScript
client.on("connection_status", ({ status }) => {
  callButton.disabled = status !== "connected";
});

This also covers reconnects — if the network drops mid-session, the client recovers on its own and fires connection_status again once it's back.

2

Placing a call

Once connected, dialing is one call:

TypeScript
await client.call(contact.phoneNumber, contact.crmId, contact.name);
  • contact.phoneNumber is dialed exactly as passed in — your CRM includes the country code if the destination needs one.
  • contact.crmId (the externalRef argument) is an opaque id echoed back untouched on every event — use it to look the call back up against your own records.
  • contact.name (the leadName argument) is display-only, shown in the built-in widget, or readable from client.getCurrentCall() in a custom UI.

If the number is on the tenant's Do Not Call list, the call is blocked server-side before anything is dialed — client.call() rejects with a 403 rather than letting it ring. No extra compliance logic is needed on your end for this case.

3

Reacting to what happens next

This is the part that trips people up: the agent's own phone connecting is not the same as the customer answering. The SDK models this as two separate events:

TypeScript
client.on("connecting", (call) => {
  // Originated, nothing is ringing anywhere yet.
});

client.on("ringing", (call) => {
  // The agent's own leg picked up — BelSmart is now dialing the CUSTOMER.
  // Don't start a call timer here.
});

client.on("active", (call) => {
  // The customer answered. Start a duration timer or "on call" indicator here.
});

client.on("ended", (call) => {
  console.log(`${call.toNumber} — ${call.duration}s`);
  // duration is measured from "active", not from when dialing started.
});

A practical rule of thumb: drive your UI off active and ended, not connecting and ringing. The latter two are useful for a "calling..." state, but duration or outcome logic should key off active/ended.

4

Controlling a call while it's live

Once a call is active, these are available immediately:

TypeScript
client.setMuted(true);
await client.hold();
client.setVolume(0.5);       // local playback only, 0–1
client.sendDtmf("1");        // e.g. navigating an IVR on the far end
client.hangup();

If you're using the built-in widget (createWidget(client)), all of this is already wired to buttons — you only need these calls if you're building a custom UI instead.

5

Adding a third party (conference)

Once a call is active, adding someone else folds it into a conference — everyone can hear each other from that point on:

TypeScript
await client.addToConference(
  supervisorNumber,
  "Jane (Supervisor)"
);

To build a roster (who's on the call, who's muted, who's on hold):

TypeScript
const participants =
  await client.listConferenceParticipants();

This has no push updates — call it again after any action to refresh. The built-in widget polls it automatically; a custom UI needs to do the same.

Ending it:

TypeScript
client.hangup();          // drops only your own leg
await client.hangupAll(); // ends the call for everyone
6

Handing off a call (attended transfer)

Unlike a conference, an attended transfer lets you privately consult someone before deciding whether to connect them to the customer:

TypeScript
await client.startConsult(supervisorNumber);
// customer is held, can't hear this conversation

// then either:
await client.completeTransfer();
// bridges customer + consult target, you drop off

// or:
await client.cancelConsult();
// drops consult target, resumes with customer

There's no built-in widget UI for this yet — it's a custom-UI-only feature today.

7

Recording an outcome

Dispositions are typically set after the call ends, once the agent has picked an outcome in your own UI:

TypeScript
client.on("ended", async (call) => {
  // show your outcome picker, then:
  await client.setDisposition(chosenOutcome.name);
});
Worth knowing: this currently writes to BelSmart's partner-call tracking record only — it's not yet reflected in BelSmart's own CDR or reporting dashboards. Treat it as data your integration can read back, not something that changes BelSmart's own reports today.

Putting it together: a minimal click-to-call button

Everything above, wired into one button.

TypeScript
import { BelsmartCrmClient } from "@belsmart/crm-sdk";

const client = new BelsmartCrmClient({
  apiBaseUrl,
  apiKey,
  apiSecret,
  agentUuid
});

await client.init();

client.on("connection_status", ({ status }) => {
  callButton.disabled = status !== "connected";
});

callButton.addEventListener("click", async () => {
  try {
    await client.call(
      contact.phoneNumber,
      contact.crmId,
      contact.name
    );
  } catch (err) {

    if (err.message?.includes("DNC")) {
      showToast(
        "This number is on the Do Not Call list."
      );

    } else {

      showToast(
        "Couldn't place the call. Try again."
      );

    }
  }
});

client.on("active", (call) => {
  startTimer(call.callId);
});

client.on("ended", (call) => {
  stopTimer(call.callId);
  showDispositionPicker(call);
});
📞 Wiring this into your CRM? Our integrations team can walk through your setup and mapping live — most integrations are working end to end within a day. Talk to Integrations →
API reference

Agent List

Look up your agent roster and build the mapping between BelSmart agents and your own CRM UI.

Static — only needs the key pair, no client instance or session required. Call it before constructing a client, to build the agent-mapping UI.
TypeScript
import { BelsmartCrmClient } from "@belsmart/crm-sdk";

const agents = await BelsmartCrmClient.listAgents({
  apiBaseUrl,
  apiKey,
  apiSecret
});

// [{ agentUuid, name, email, extension }, ...]

Returned agent fields

Field Type Description
agentUuid string Unique identifier for the agent.
name string Agent's display name.
email string Agent's account email.
extension string Agent's internal dialing extension.
API reference

Call Detail Records (CDR)

Fetches the gateway's own call detail record — numbers, timestamps, duration, hangup cause and disposition.

callId is optional — omit it to default to the most recently ended call, or pass one explicitly to look up an older call (e.g. one saved in your own DB).
TypeScript
const cdr = await client.getCallCdr(callId);
// or client.getCallCdr() for the last ended call

// {
//   callId,
//   direction,
//   fromNumber,
//   toNumber,
//   startedAt,
//   answeredAt,
//   endedAt,
//   duration,
//   billableDuration,
//   hangupCause,
//   disposition,
//   externalRef
// }

Returned CDR fields

Field Type Description
callId string Unique identifier for the call.
direction string Call direction, e.g. inbound or outbound.
fromNumber string Originating number.
toNumber string Destination number.
startedAt string Timestamp the call was initiated.
answeredAt string Timestamp the call was answered, if it was.
endedAt string Timestamp the call ended.
duration number Total call length.
billableDuration number Portion of the call counted for billing.
hangupCause string Gateway-level reason the call ended.
disposition string Call outcome, e.g. connected, no answer, voicemail.
externalRef string Your own reference ID, if one was attached to the call.
API reference

Recording

Fetches the recording's playback/download URL for a call.

Returns null if the call wasn't recorded or the gateway hasn't finished processing it yet — worth a short retry/poll in that case. Same optional callId convention as getCallCdr().
TypeScript
const recording =
  await client.getCallRecording(callId);

// or client.getCallRecording()

if (recording) {

  // {
  //   callId,
  //   url,
  //   duration,
  //   format,
  //   availableAt
  // }

  console.log(
    "play/download at:",
    recording.url
  );
}

Returned recording fields

Field Type Description
callId string The call this recording belongs to.
url string Playback/download URL for the recording.
duration number Length of the recording.
format string Audio format of the recording.
availableAt string Timestamp the recording became available.