Node.js / TypeScript SDK (bessai)
The official Node.js SDK for the BESS AI Voice Platform. Fully typed, async-only
(every method returns a Promise), built on native fetch, with automatic retry
and exponential backoff for rate limits and transient server errors. This page
documents every public method on the client, its TypeScript signature, and the
REST endpoint it maps to.
- Install:
npm install bessai - Requirements: Node.js 18+ (native
fetch); TypeScript 5.0+ optional - Base URL (production):
https://api.bess-ai.com - Module formats: ESM and CommonJS (dual build)
- Support: contact@bess-ai.com
1. Install and authenticate
npm install bessai
Get your API key from the BESS AI dashboard → Settings → API Keys. The key
looks like bess_sk_live_xxxxxxxx... and is shown only once at creation —
store it securely.
Set it as an environment variable (the SDK reads it automatically):
export BESSAI_API_KEY="bess_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
import BessAI from "bessai";
// or: import { BessAI } from "bessai";
const client = new BessAI(); // reads BESSAI_API_KEY from the environment
CommonJS works too:
const { BessAI } = require("bessai");
const client = new BessAI({ apiKey: "bess_..." });
Client options
const client = new BessAI({
apiKey: "bess_...", // or env BESSAI_API_KEY
baseUrl: "https://api.bess-ai.com", // or env BESSAI_BASE_URL
timeout: 30_000, // per-request timeout in ms
maxRetries: 3, // retries for 429/5xx and network errors
headers: {}, // extra headers merged into every request
});
| Option | Type | Default | Notes |
|---|---|---|---|
apiKey | string | BESSAI_API_KEY env | Required. The constructor throws if neither is set. |
baseUrl | string | https://api.bess-ai.com | Override with BESSAI_BASE_URL (trailing slashes are stripped). |
timeout | number | 30000 | Per-request timeout in milliseconds. |
maxRetries | number | 3 | Auto-retries on 429/500/502/503/504, timeouts, and connection errors, with exponential backoff that honors Retry-After. |
headers | Record<string, string> | {} | Extra default headers. |
Every request sends Authorization: Bearer <apiKey>.
Resource namespaces
All resources are properties on the client:
| Property | Class | Methods |
|---|---|---|
client.agent | AgentResource | 12 |
client.call | CallResource | 8 |
client.phoneNumber | PhoneNumberResource | 12 (+1 alias) |
client.batchCall | BatchCallResource | 10 (+3 aliases) |
client.workflow | WorkflowResource | 25 |
client.analytics | AnalyticsResource | 3 |
client.billing | BillingResource | 9 |
client.config | ConfigResource | 4 |
client.knowledgeBases | KnowledgeBasesResource | 6 |
client.apiKeys | APIKeysResource | 7 |
client.streaming | StreamingResource | 1 |
Plural aliases point at the same instances: client.agents, client.calls,
client.phoneNumbers, client.batchCalls, client.workflows.
The low-level HTTP client is exposed as client.httpClient
(get / post / patch / put / delete / request / getBytes) for
advanced use.
Request and response conventions
- Request bodies use the API's snake_case field names (
agent_name,from_number,system_prompt, ...) — exactly as documented in the REST reference. Method names and option-object wrappers are camelCase. - List endpoints return
PaginatedResponse<T>:
interface PaginatedResponse<T> {
items: T[];
total: number;
skip: number;
limit: number;
}
There is no automatic paginator — loop with increasing skip until you receive
fewer than limit items.
2. Quickstart
Create an agent, publish it, and place an outbound phone call:
import BessAI from "bessai";
const client = new BessAI(); // BESSAI_API_KEY from env
// 1. Create a draft agent
const agent = await client.agent.create({
agent_name: "Support Bot",
llm_provider: "openai",
llm_model: "gpt-4o-mini",
voice_provider: "elevenlabs",
voice_id: "21m00Tcm4TlvDq8ikWAM",
system_prompt: "You are a friendly support agent for Acme Co.",
greeting_message: "Hello! How can I help you today?",
language: "en-US",
});
// 2. Publish it — only published agents can take phone/web calls
await client.agent.publish(agent.agent_id);
// 3. Place a call
const call = await client.call.createPhoneCall({
agent_id: agent.agent_id,
from_number: "+14155550123", // a number you own
to_number: "+14155550199",
dynamic_variables: { customer_name: "Jordan" },
});
console.log(call.call_id, call.status);
3. Errors
All SDK errors extend BessAIError and carry message, statusCode, and
body (the parsed error response). Import them from the package root:
import {
BessAIError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError,
} from "bessai";
try {
await client.agent.retrieve("does-not-exist");
} catch (e) {
if (e instanceof NotFoundError) {
console.log("No such agent");
} else if (e instanceof RateLimitError) {
console.log(`Slow down — retry after ${e.retryAfter}s`);
} else if (e instanceof AuthenticationError) {
console.log("Bad or missing API key");
} else if (e instanceof BessAIError) {
console.log(`API error ${e.statusCode}: ${e.message}`);
}
}
| Error class | HTTP | Meaning |
|---|---|---|
AuthenticationError | 401 | Missing, invalid, or expired API key. |
PermissionDeniedError | 403 | The key's scopes do not allow this operation. |
NotFoundError | 404 | Resource does not exist. |
ValidationError | 422 | Invalid input. Has .errors (list of field errors from the API). |
RateLimitError | 429 | Rate limit hit. Has .retryAfter (seconds, when provided). |
InternalServerError | 5xx | Server-side error. |
ConnectionError | — | Could not reach the server. |
TimeoutError | — | The request timed out. |
BessAIError | — | Base class for all of the above. |
Retryable statuses (429, 500, 502, 503, 504), timeouts, and connection
errors are retried automatically up to maxRetries times before the error is
thrown.
4. Agents — client.agent
Agents define the LLM, STT, TTS, voice, and behavior of your voice AI. Create a
draft, then publish() to make it callable. Every update() saves a new draft
version.
create(params: AgentCreateParams): Promise<AgentResponse>
POST /v1/agents — create a new agent in draft state. Only agent_name is
required; everything else has a sensible server-side default.
Key AgentCreateParams fields (all snake_case, all optional unless noted):
| Field | Type | Description |
|---|---|---|
agent_name | string | Required. Display name (1–255 chars). Sent to the API as name. |
description | string | Internal description. |
agent_type | string | orchestration_agent (STT → LLM → TTS, default) or realtime_agent (speech-to-speech). |
agent_mode | string | single_prompt (default) or conversation_flow. |
llm_provider / llm_model | string | e.g. openai / gpt-4o, anthropic, groq. |
system_prompt | string | Main instructions. |
greeting_message | string | First utterance; empty = agent waits for the user. |
temperature | number | 0–2. |
max_completion_tokens | number | 1–8192. |
voice_provider / voice_id | string | e.g. elevenlabs, cartesia + provider voice id. |
voice_speed | number | 0.5–2.0. |
stt_provider / stt_model | string | e.g. deepgram / nova-2. |
stt_multilingual | boolean | Auto language detection. |
realtime_provider / realtime_model / realtime_voice | string | For realtime_agent types. |
language | string | BCP-47 tag (en-US, tr-TR, ...). |
interruption_sensitivity | number | 0–1. |
end_call_after_silence_ms | number | Auto-end after silence. |
max_call_duration_ms | number | Hard cap on call length. |
knowledge_base_ids | string[] | RAG knowledge bases to attach. |
webhook_url | string | Webhook for call events. |
mcp_config | Record<string, unknown> | MCP server config (external tools). |
native_tools_config | Record<string, unknown> | Built-in tools (end_call, transfer_to_human, ...). |
Additional fine-tuning fields: llm_base_url, llm_api_key, tts_model,
volume, emotion, tts_base_url, tts_sample_rate, tts_use_websocket,
stt_base_url, stt_api_key, voice_stability, responsiveness,
endpointing_ms, enable_backchannel, response_delay_ms,
reminder_trigger_ms, reminder_max_count, background_sound,
analytics_prompt, analytics_model_provider, analytics_model,
enable_sentiment_analysis, enable_summary_generation,
post_call_analysis_config, version_description.
Returns AgentResponse: agent_id, agent_name, description,
is_published, published_version, agent_type, agent_mode, created_at,
updated_at, versions (array of AgentVersion config snapshots),
linked_workflows.
const agent = await client.agent.create({
agent_name: "Reservations Bot",
agent_type: "orchestration_agent",
llm_provider: "openai",
llm_model: "gpt-4o",
voice_provider: "elevenlabs",
voice_id: "21m00Tcm4TlvDq8ikWAM",
system_prompt: "You take restaurant reservations. Be concise and friendly.",
language: "en-US",
});
retrieve(agentId: string): Promise<AgentResponse>
GET /v1/agents/{agent_id} — full agent including version history.
list(skip = 0, limit = 50): Promise<PaginatedResponse<AgentResponse>>
GET /v1/agents — all agents in your organization.
const { items, total } = await client.agent.list(0, 50);
update(agentId: string, params: AgentUpdateParams): Promise<AgentResponse>
PATCH /v1/agents/{agent_id} — change any field from create (all optional).
Saves a new draft version; call publish() to go live.
delete(agentId: string): Promise<Record<string, unknown>>
DELETE /v1/agents/{agent_id} — permanently delete the agent and all versions.
publish(agentId: string, version?: number): Promise<AgentResponse>
POST /v1/agents/{agent_id}/publish — publish the current draft (or a specific
version, sent as a query parameter) as the live version. Only published
agents can take phone/web calls.
getVersions(agentId: string): Promise<Record<string, unknown>[]>
GET /v1/agents/{agent_id}/versions — full version history.
export(agentId: string): Promise<Record<string, unknown>>
GET /v1/agents/{agent_id}/export — portable JSON you can re-import.
importAgent(data: Record<string, unknown>): Promise<AgentResponse>
POST /v1/agents/import — create an agent from previously exported JSON.
Takes the parsed JSON object (read the file yourself first).
import { readFileSync } from "node:fs";
const data = JSON.parse(readFileSync("./agent-export.json", "utf8"));
const imported = await client.agent.importAgent(data);
linkWorkflow(agentId, workflowId, options?): Promise<Record<string, unknown>>
POST /v1/agents/{agent_id}/workflows/{workflow_id} — link a workflow to an
agent. Full signature:
linkWorkflow(
agentId: string,
workflowId: string,
options?: {
triggerConditionDescription?: string;
priority?: number;
executionModeOverride?: string;
}
): Promise<Record<string, unknown>>
unlinkWorkflow(agentId: string, workflowId: string): Promise<Record<string, unknown>>
DELETE /v1/agents/{agent_id}/workflows/{workflow_id} — unlink a workflow.
listWorkflows(agentId: string): Promise<Record<string, unknown>[]>
GET /v1/agents/{agent_id}/workflows — workflows linked to this agent.
5. Calls — client.call
Place phone calls, browser (web) calls, and test calls; retrieve transcripts, recordings, and analytics.
CallResponse fields: call_id, call_type, status
(waiting → ringing → connected → ended / failed), agent_id,
from_number, to_number, access_token, room_name, room_url,
start_time, end_time, duration_seconds, transcript, recording_url,
call_analysis, call_summary, call_sentiment, call_cost,
latency_metrics, processing_status, workflow_executions, created_at.
createPhoneCall(params: PhoneCallCreateParams): Promise<CallResponse>
POST /v1/calls/phone — place an outbound PSTN call immediately.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | A published agent. |
from_number | string | yes | Caller ID in E.164 (+14155550123); a number you own. |
to_number | string | yes | Destination in E.164. |
metadata | Record<string, unknown> | no | Arbitrary metadata stored on the call. |
dynamic_variables | Record<string, string> | no | Values injected into the system prompt. |
batch_call_id | string | no | Link this call to a batch campaign. |
const call = await client.call.createPhoneCall({
agent_id: "ag_abc123",
from_number: "+14155550123",
to_number: "+14155550199",
dynamic_variables: { customer_name: "Jordan" },
});
createWebCall(params: WebCallCreateParams): Promise<CallResponse>
POST /v1/calls/web — browser-to-agent call over WebRTC. Params: agent_id
(required), metadata, dynamic_variables. The response includes
access_token, room_name, and room_url for the LiveKit client SDK to
connect.
createTestCall(params: TestCallCreateParams): Promise<CallResponse>
POST /v1/calls/test — like a web call but accepts draft agents and a
temp_config object of non-persisted overrides. Params: agent_id (required),
temp_config, dynamic_variables. The fastest loop for iterating on an agent
during development.
retrieve(callId: string): Promise<CallResponse>
GET /v1/calls/{call_id} — full call details. Poll this after a call ends for
the transcript, recording URL, and analytics.
list(params?: CallListParams): Promise<PaginatedResponse<CallListItem>>
GET /v1/calls — list calls with optional filters:
interface CallListParams {
skip?: number;
limit?: number;
agent_id?: string;
status?: string;
call_type?: string;
batch_call_id?: string; // a UUID, "none" (single calls), or "any" (batch calls)
from_date?: string; // ISO 8601
to_date?: string; // ISO 8601
}
const calls = await client.call.list({
agent_id: "ag_abc123",
status: "completed",
from_date: "2026-01-01",
});
end(callId: string): Promise<Record<string, unknown>>
POST /v1/calls/{call_id}/end — end an active call and trigger post-call
processing.
delete(callId: string): Promise<Record<string, unknown>>
DELETE /v1/calls/{call_id} — delete a call record.
getRecording(callId: string): Promise<ArrayBuffer>
GET /v1/calls/{call_id}/recording — the recording audio file as raw bytes
(typically OGG). Check retrieve(...).recording_url first to confirm a
recording exists.
import { writeFileSync } from "node:fs";
const audio = await client.call.getRecording(call.call_id);
writeFileSync("./recording.ogg", Buffer.from(audio));
6. Phone Numbers — client.phoneNumber
Register numbers and manage their SIP connections and dispatch rules.
create(params: PhoneNumberCreateParams): Promise<PhoneNumberResponse>
POST /v1/phone-numbers — register a phone number.
| Field | Type | Required | Description |
|---|---|---|---|
phone_number | string | yes | E.164 number. |
nickname | string | no | Friendly name. |
provider | string | no | e.g. custom, netgsm, twilio, telnyx. |
inbound_agent_id | string | no | Agent for inbound calls. |
outbound_agent_id | string | no | Agent for outbound calls. |
allowed_inbound_countries | string[] | no | Inbound country allowlist (default all). |
allowed_outbound_countries | string[] | no | Outbound country allowlist (default all). |
inbound_webhook_url | string | no | Webhook for inbound events. |
Returns PhoneNumberResponse: phone_number_id, phone_number,
nickname, provider, inbound_agent_id, outbound_agent_id, status,
created_at.
retrieve(phoneNumberId: string): Promise<PhoneNumberDetailResponse>
GET /v1/phone-numbers/{id} — detail view including sip_connections and
dispatch_rules, agent names, country allowlists.
get(phoneNumberId: string): Promise<PhoneNumberDetailResponse>
Alias for retrieve().
list(skip = 0, limit = 50): Promise<PaginatedResponse<PhoneNumberResponse>>
GET /v1/phone-numbers — all numbers in your organization.
update(phoneNumberId: string, params: PhoneNumberUpdateParams): Promise<PhoneNumberDetailResponse>
PATCH /v1/phone-numbers/{id} — update nickname, provider,
inbound_agent_id, outbound_agent_id, country allowlists, or
inbound_webhook_url.
await client.phoneNumber.update(pn.phone_number_id, {
inbound_agent_id: "ag_xyz",
outbound_agent_id: "ag_abc",
});
delete(phoneNumberId: string): Promise<Record<string, unknown>>
DELETE /v1/phone-numbers/{id} — deletes the number and its SIP connections.
updateAgents(phoneNumberId: string, params: PhoneNumberAgentUpdateParams): Promise<PhoneNumberDetailResponse>
PATCH /v1/phone-numbers/{id}/agents — update only the agent assignment.
Params: inbound_agent_id, outbound_agent_id (both optional).
SIP connections
A SIP connection carries the carrier trunk details (termination URI + credentials) and auto-syncs to the telephony layer.
createSipConnection(phoneNumberId: string, params: SIPConnectionCreateParams): Promise<SIPConnectionResponse>
POST /v1/phone-numbers/{id}/sip-connections
| Field | Type | Required | Description |
|---|---|---|---|
termination_uri | string | yes | Carrier SIP URI. |
username / password | string | no | Trunk credentials. |
nickname | string | no | Friendly name. |
connection_type | string | no | inbound, outbound, or both. |
transport | string | no | TCP, UDP, or TLS. |
await client.phoneNumber.createSipConnection(pn.phone_number_id, {
termination_uri: "sip:trunk.provider.com",
username: "user",
password: "pass",
connection_type: "both",
});
getSipConnection(phoneNumberId: string, connectionId: string): Promise<SIPConnectionResponse>
GET /v1/phone-numbers/{id}/sip-connections/{connection_id}
listSipConnections(phoneNumberId: string): Promise<SIPConnectionResponse[]>
GET /v1/phone-numbers/{id}/sip-connections
updateSipConnection(phoneNumberId: string, connectionId: string, params: SIPConnectionUpdateParams): Promise<SIPConnectionResponse>
PATCH /v1/phone-numbers/{id}/sip-connections/{connection_id} — same fields as
create, all optional.
deleteSipConnection(phoneNumberId: string, connectionId: string): Promise<Record<string, unknown>>
DELETE /v1/phone-numbers/{id}/sip-connections/{connection_id}
SIPConnectionResponse: sip_connection_id, phone_number_id,
termination_uri, username, nickname, connection_type, transport,
livekit_trunk_id, sync_status, sync_error, created_at, updated_at.
listDispatchRules(phoneNumberId: string): Promise<SIPDispatchRuleResponse[]>
GET /v1/phone-numbers/{id}/dispatch-rules — the SIP dispatch rules that route
inbound calls on this number to an agent.
7. Batch Calls — client.batchCall
Run outbound calling campaigns over a list of contacts.
create(params: BatchCallCreateParams): Promise<BatchCallResponse>
POST /v1/batch-calls — create a campaign (does not start dialing; call
start() next).
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | Agent that makes the calls. |
from_number | string | yes | Registered caller ID. |
contacts | BatchCallContact[] | yes | Each: { phone_number, dynamic_variables? }. |
name | string | no | Campaign name. |
max_concurrent_calls | number | no | Simultaneous calls. |
retry_attempts | number | no | Retries per failed contact. |
const batch = await client.batchCall.create({
agent_id: "ag_abc123",
from_number: "+14155550123",
name: "March outreach",
contacts: [
{ phone_number: "+14155551001", dynamic_variables: { name: "Sam" } },
{ phone_number: "+14155551002" },
],
max_concurrent_calls: 10,
});
await client.batchCall.start(batch.batch_call_id);
retrieve(batchCallId: string): Promise<BatchCallStatusResponse>
GET /v1/batch-calls/{batch_id} — live status and counts: batch_call_id,
name, status, total, pending, active, completed, failed,
processed, counts, progress_percent, started_at, completed_at.
get(batchCallId) / getStatus(batchCallId)
Aliases for retrieve().
list(skip = 0, limit = 50): Promise<PaginatedResponse<BatchCallResponse>>
GET /v1/batch-calls — all campaigns.
listActive(): Promise<BatchCallResponse[]>
GET /v1/batch-calls/active — currently running campaigns.
listItems(batchCallId: string, skip = 0, limit = 100): Promise<PaginatedResponse<BatchCallItemResponse>>
GET /v1/batch-calls/{batch_id}/items — per-contact results (item_id,
phone_number, status, attempt_count, call_id, error_message,
started_at, completed_at). getItems(...) is an alias.
delete(batchCallId: string): Promise<Record<string, unknown>>
DELETE /v1/batch-calls/{batch_id} — delete a campaign (only when not
running).
Lifecycle controls
start(batchCallId: string)—POST /v1/batch-calls/{batch_id}/startpause(batchCallId: string)—POST /v1/batch-calls/{batch_id}/pauseresume(batchCallId: string)—POST /v1/batch-calls/{batch_id}/resumecancel(batchCallId: string)—POST /v1/batch-calls/{batch_id}/cancel
Each returns Promise<Record<string, unknown>>.
8. Workflows — client.workflow
AI-powered automation (n8n under the hood) triggered by calls, webhooks, or schedules.
Create and generate
create(params: WorkflowCreateParams): Promise<WorkflowCreateResponse>
POST /v1/workflows — save n8n workflow JSON directly. Fields: name
(required), trigger_type (required — post_call, in_call, webhook, or
schedule), workflow_json (required), description, trigger_config,
execution_mode, timeout_seconds.
generate(params: WorkflowGenerateParams): Promise<GenerateResponse>
POST /v1/workflows/generate — AI-generate a workflow from a natural-language
description. Fields: name (required), description (required),
trigger_type, trigger_config, agent_id. Returns workflow_id,
workflow_json, required_secrets, visualization.
const wf = await client.workflow.generate({
name: "Lead Qualifier",
description: "After each call, send qualified leads to the CRM via webhook.",
});
refine(workflowId: string, params: WorkflowRefineParams): Promise<GenerateResponse>
POST /v1/workflows/{workflow_id}/refine — AI-refine an existing workflow.
Params: { feedback: string }.
Read, update, delete
retrieve(workflowId: string): Promise<WorkflowDetailResponse>
GET /v1/workflows/{workflow_id} — includes workflow_json,
detected_credentials, generation_history, n8n_workflow_id, timestamps.
list(params?: WorkflowListParams): Promise<PaginatedResponse<WorkflowResponse>>
GET /v1/workflows — filters: trigger_type, status, agent_id,
include_deleted, page, per_page. (Note: this resource paginates with
page/per_page, not skip/limit.)
update(workflowId: string, params: WorkflowUpdateParams): Promise<Record<string, unknown>>
PATCH /v1/workflows/{workflow_id} — name, description, is_active,
status, execution_mode, timeout_seconds.
delete(workflowId: string): Promise<Record<string, unknown>>
DELETE /v1/workflows/{workflow_id}
restore(workflowId: string, version: number): Promise<WorkflowDetailResponse>
POST /v1/workflows/{workflow_id}/restore — restore a previous version.
Secrets and credentials
saveSecrets(workflowId: string, params: WorkflowSecretsParams): Promise<Record<string, unknown>>
POST /v1/workflows/{workflow_id}/secrets — save encrypted secrets. secrets
is an array of { key_name, value, type? }; optional validate_credentials.
getCredentialSchema(workflowId: string): Promise<CredentialSchemaResponse[]>
GET /v1/workflows/{workflow_id}/credential-schema — which credentials the
workflow needs and their field schemas.
Deploy and execute
deploy(workflowId: string): Promise<DeployResponse>
POST /v1/workflows/{workflow_id}/deploy — push to the execution engine and
activate. Returns status, workflow_id, n8n_workflow_id, error.
test(workflowId: string): Promise<ExecuteResponse>
POST /v1/workflows/{workflow_id}/test — test-execute.
execute(workflowId: string, params?: ManualExecuteParams): Promise<ExecuteResponse>
POST /v1/workflows/{workflow_id}/execute — manual execution with optional
context_data and execution_mode. Returns status, execution_id,
execution_time_ms, output, error.
listExecutions(workflowId: string, skip = 0, limit = 50, status?: string): Promise<PaginatedResponse<WorkflowExecutionResponse>>
GET /v1/workflows/{workflow_id}/executions — past executions with status,
timing, error details, and input/output snapshots.
await client.workflow.saveSecrets(wf.workflow_id!, {
secrets: [{ key_name: "CRM_API_KEY", value: "sk_..." }],
});
await client.workflow.deploy(wf.workflow_id!);
const result = await client.workflow.test(wf.workflow_id!);
Export and import
export(workflowId: string): Promise<Record<string, unknown>>—GET /v1/workflows/{workflow_id}/exportimportWorkflow(data: Record<string, unknown>): Promise<WorkflowResponse>—POST /v1/workflows/import(takes the parsed JSON object)
Agent linking
linkAgent(workflowId: string, agentId: string, params?: LinkAgentParams): Promise<AgentWorkflowLinkResponse>—POST /v1/workflows/{workflow_id}/agents/{agent_id}. Params:trigger_condition_description,priority,execution_mode_override.updateAgentLink(workflowId: string, agentId: string, params: UpdateAgentLinkParams): Promise<AgentWorkflowLinkResponse>—PATCH /v1/workflows/{workflow_id}/agents/{agent_id}. Addsis_enabled.unlinkAgent(workflowId: string, agentId: string): Promise<Record<string, unknown>>—DELETE /v1/workflows/{workflow_id}/agents/{agent_id}listAgents(workflowId: string): Promise<AgentWorkflowListItem[]>—GET /v1/workflows/{workflow_id}/agentslistByAgent(agentId: string): Promise<AgentWorkflowListItem[]>—GET /v1/agents/{agent_id}/workflows
Scheduling
setSchedule(workflowId: string, config: Record<string, unknown>): Promise<ScheduleStatusResponse>—POST /v1/workflows/{workflow_id}/schedule(workflow must havetrigger_type: "schedule"; config supports cron/interval settings).getSchedule(workflowId: string): Promise<ScheduleStatusResponse>—GET /v1/workflows/{workflow_id}/scheduleremoveSchedule(workflowId: string): Promise<Record<string, unknown>>—DELETE /v1/workflows/{workflow_id}/schedulelistSchedules(): Promise<ScheduleStatusResponse[]>—GET /v1/workflows/schedules— all scheduled workflows in the organization.
9. Analytics — client.analytics
All three methods take the same optional arguments: fromDate and toDate
(ISO 8601 strings, sent as from_date / to_date) and agentId (sent as
agent_id).
getSummary(fromDate?, toDate?, agentId?): Promise<AnalyticsSummary>
GET /v1/analytics/summary — aggregate stats: total_calls,
completed_calls, failed_calls, total_duration_seconds,
average_duration_seconds, success_rate, from_date, to_date.
const summary = await client.analytics.getSummary("2026-01-01", "2026-01-31");
console.log(`${summary.total_calls} calls, ${summary.success_rate}% success`);
getLatency(fromDate?, toDate?, agentId?): Promise<LatencyMetrics>
GET /v1/analytics/latency — latency percentiles for e2e, stt, llm, and
tts, each with p50 / p90 / p95 / p99.
getCallsByDay(fromDate?, toDate?, agentId?): Promise<CallsByDay[]>
GET /v1/analytics/calls-by-day — array of { date, count }.
10. Billing — client.billing
getBalance(): Promise<CreditBalanceResponse>
GET /v1/billing/balance — current_balance, total_credits_added,
total_credits_used, organization_id.
checkBalance(amount: number): Promise<BalanceCheckResponse>
GET /v1/billing/balance/check — is the balance sufficient for amount?
Returns sufficient, balance, required, min_balance, shortfall.
listTransactions(limit = 50, offset = 0, transactionType?: string): Promise<TransactionListResponse>
GET /v1/billing/transactions — credit ledger entries (entries, total,
limit, offset).
listUsage(limit = 50, offset = 0, eventType?: string): Promise<UsageListResponse>
GET /v1/billing/usage — raw usage events (events, total, limit,
offset).
getUsageSummary(fromDate?: string, toDate?: string): Promise<UsageSummaryResponse>
GET /v1/billing/usage/summary — period, total_events, total_price_usd,
breakdown.
getDailyUsage(fromDate?: string, toDate?: string): Promise<DailyUsageItem[]>
GET /v1/billing/usage/daily — array of { date, event_count, total_price_usd }.
getCallUsage(callId: string): Promise<CallUsageResponse>
GET /v1/billing/usage/calls/{call_id} — usage events and total cost for one
call.
getPricing(): Promise<ServicePricingResponse>
GET /v1/billing/pricing — voice_call_pricing, service_pricing,
credit_config.
estimatePricing(params: PricingEstimateParams): Promise<PricingEstimateResponse>
POST /v1/billing/pricing/estimate — estimate per-minute cost for an agent
configuration. Params (all optional): agent_type, stt_provider,
stt_model, llm_provider, llm_model, voice_provider,
realtime_provider, realtime_model. Returns total_per_minute,
pricing_type, components, post_call_analytics_per_execution.
const est = await client.billing.estimatePricing({
llm_provider: "openai",
llm_model: "gpt-4o",
stt_provider: "deepgram",
voice_provider: "elevenlabs",
});
console.log(est.total_per_minute);
11. Config — client.config
Describes what the platform supports (providers, models, languages, defaults).
getProviders(): Promise<ProviderConfig>
GET /v1/config/providers — full catalog: stt, llm, tts, realtime,
analytics, languages, defaults.
getProvider(category: string): Promise<Record<string, unknown>>
GET /v1/config/providers/{category} — one category: stt, llm, tts, or
realtime.
getDefaults(): Promise<DefaultsConfig>
GET /v1/config/defaults — platform default provider/model selections.
getLanguages(): Promise<LanguageEntry[]>
GET /v1/config/languages — supported languages (code, name,
native_name).
12. Knowledge Bases — client.knowledgeBases
RAG document stores you can attach to agents via knowledge_base_ids.
create(params: KnowledgeBaseCreateParams): Promise<KnowledgeBase>
POST /v1/knowledge-bases — params: name (required), description.
list(): Promise<KnowledgeBase[]>
GET /v1/knowledge-bases
get(knowledgeBaseId: string): Promise<KnowledgeBase>
GET /v1/knowledge-bases/{kb_id} — includes documents (each with id,
filename, file_type, file_size_bytes, status, chunk_count,
created_at).
delete(knowledgeBaseId: string): Promise<Record<string, unknown>>
DELETE /v1/knowledge-bases/{kb_id}
uploadDocument(knowledgeBaseId: string, file: Blob | Buffer, filename: string): Promise<Record<string, unknown>>
POST /v1/knowledge-bases/{kb_id}/documents — multipart upload. Accepts a
Blob or a Node Buffer.
import { readFileSync } from "node:fs";
const kb = await client.knowledgeBases.create({
name: "Product Docs",
description: "Product documentation for agent reference",
});
const buf = readFileSync("./docs.pdf");
await client.knowledgeBases.uploadDocument(kb.id, buf, "docs.pdf");
deleteDocument(knowledgeBaseId: string, documentId: string): Promise<Record<string, unknown>>
DELETE /v1/knowledge-bases/{kb_id}/documents/{document_id}
13. API Keys — client.apiKeys
Programmatically manage keys for your organization.
create(params: APIKeyCreateParams): Promise<APIKeyCreated>
POST /v1/api-keys
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Friendly name. |
scopes | string[] | no | e.g. ["agents:read", "calls:write"]; ["*"] = full access. |
expires_in_days | number | no | Key lifetime. |
rate_limit_tier | string | no | free, starter, professional, enterprise. |
ip_allowlist | string[] | no | IPs/CIDRs allowed to use the key; empty = all. |
Returns APIKeyCreated — includes key, the full plaintext key, shown
only once. Store it immediately.
const key = await client.apiKeys.create({
name: "Production Key",
scopes: ["agents:read", "calls:write"],
expires_in_days: 90,
});
console.log(key.key); // shown only once
list(): Promise<APIKey[]>
GET /v1/api-keys — all keys, masked (only key_prefix is shown).
get(keyId: string): Promise<APIKey>
GET /v1/api-keys/{key_id}
update(keyId: string, params: APIKeyUpdateParams): Promise<APIKey>
PATCH /v1/api-keys/{key_id} — name, scopes, rate_limit_tier,
ip_allowlist, is_active.
delete(keyId: string): Promise<Record<string, unknown>>
DELETE /v1/api-keys/{key_id} — revoke immediately.
rotate(keyId: string): Promise<APIKeyCreated>
POST /v1/api-keys/{key_id}/rotate — generate a new secret for the key. The
response includes the new plaintext key (shown only once); the old key
remains valid during the grace period.
getUsage(keyId: string): Promise<APIKeyUsage>
GET /v1/api-keys/{key_id}/usage — total_requests, last_used_at,
last_used_ip, rate_limit_tier, rate_limits, is_active, expires_at.
14. Streaming — client.streaming
Real-time events over WebSocket (requires the ws package, which is a bundled
dependency of the SDK).
batchCallStatus(campaignId: string): BatchCallStream
Returns a BatchCallStream, an AsyncIterable<StreamEvent> that connects to
wss://api.bess-ai.com/ws/batch-call/{campaign_id} (authenticated with your
API key) and yields live status events for a running batch campaign:
const batch = await client.batchCall.create({ /* ... */ });
await client.batchCall.start(batch.batch_call_id);
for await (const event of client.streaming.batchCallStatus(batch.batch_call_id)) {
console.log(event);
}
BatchCallStream also exposes connect(): Promise<this> and
close(): Promise<void> for manual lifecycle control; iteration auto-connects
and auto-closes.
15. Parity notes (vs. the Python SDK)
The Node and Python SDKs mirror the same resource model. Known differences in
the current Node release (bessai 0.1.x):
- No
chatnamespace. The Python SDK'sclient.chat(chat sessions:create,create_test_session,send_message,retrieve,list,close) has no Node equivalent yet. Until it ships, call the REST endpoints directly (POST /v1/chat/sessions,POST /v1/chat/sessions/{id}/messages, ...) —client.httpClientworks well for this — or use the Python SDK. - No
syncSipConnectionmethod. Python hasphone_number.sync_sip_connection(...)to force a re-sync of a SIP connection; the Node SDK does not expose it. - Naming: the analytics namespace is
client.analyticsin Node (Python uses singularclient.analyticwith ananalyticsalias). - Import helpers take objects, not file paths.
agent.importAgent(data)andworkflow.importWorkflow(data)accept parsed JSON objects; the Python equivalents accept a file path. - The Python reference's warning about
phone_number.update_agents()is a Python-only bug — the NodephoneNumber.updateAgents()method is implemented normally.