API reference

Every request goes to /api/v1. Authenticated endpoints need Authorization: Bearer <accessToken>. The machine-readable version is at /openapi.json.

Errors

Non-2xx responses all use this shape:

{ "error": { "code": "not_found", "message": "No resource with id num_99", "details": {} } }

Money is in US cents, phone numbers in E.164, and timestamps in ISO-8601 UTC.

accounts

The Accounts service (/api/v1): signup, login, tokens, the account and its users, and provider staff login.

POST/api/v1/signup/setup-intentssignup.setupIntent

Step 1 of signup. Returns a SetupIntent client_secret (`clientSecret`) and the gateway's `publishableKey` for Stripe.js: call `stripe.confirmCardSetup(clientSecret, ...)` in the browser, then pass the `id` to POST /api/v1/auth/register. Card numbers never reach this API. The same card may be used for any number of accounts.

Response
SetupIntent
Type definitions (2)
/** Pass `clientSecret` to Stripe.js `confirmCardSetup` in the browser. */
export interface SetupIntent {
  id: ID;
  clientSecret: string;
  /** Gateway publishable key for initializing Stripe.js. */
  publishableKey: string | null;
}

export type ID = string;
POST/api/v1/auth/registerauth.register

Step 2 of signup. Creates the account and its admin user, saves the confirmed card through Billing, buys the plan `planId` (charged now, prorated to the end of the first 7-day period), and signs the admin in. If the charge is declined, nothing is created (402 `payment_failed`). The time zone is fixed after signup.

Request body
SignupRequest
Response
AuthResponse
Type definitions (12)
export interface SignupRequest {
  businessName: string;
  timeZone: string;
  adminName: string;
  adminEmail: string;
  password: string;
  /** Billing product code of the base plan, e.g. "plan_standard". */
  planId: string;
  /** A SetupIntent from POST /api/v1/signup/setup-intents that the browser confirmed with Stripe.js. */
  setupIntentId: ID;
}

/** Pass `clientSecret` to Stripe.js `confirmCardSetup` in the browser. */
export interface SetupIntent {
  id: ID;
  clientSecret: string;
  /** Gateway publishable key for initializing Stripe.js. */
  publishableKey: string | null;
}

export type ID = string;

export interface AuthResponse {
  user: User;
  account: Account;
  /** RS256 JWT. Send as `Authorization: Bearer <accessToken>` to Accounts and Billing. */
  accessToken: string;
  expiresIn: number;
  /** Single use: POST /api/v1/auth/refresh returns a new one. */
  refreshToken: string;
  refreshExpiresIn: number;
  tokenType: "Bearer";
  /** Only on registration: `pending_payment` if the plan charge couldn't be confirmed yet. */
  planStatus?: "active" | "pending_payment" | null;
}

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

/**
 * The business account. Identity fields come from Accounts; the rest come live
 * from Billing and are all null when Billing can't be reached (`billingAvailable: false`).
 */
export interface Account {
  id: ID;
  businessName: string;
  /** IANA zone, fixed after signup. Governs billing periods and business hours. */
  timeZone: string;
  /** SIP domain phones register at. */
  sipDomain: string;
  createdAt: ISODate;
  billingAvailable: boolean;
  status: AccountStatus | null;
  /** `pending` until the base plan is paid; no calling until `active`. */
  activationState: "pending" | "active" | null;
  /** `blocked` when outgoing calls are blocked. */
  restriction: "active" | "blocked" | null;
  canCallOutgoing: boolean | null;
  /** Calls outside the US, including other +1 countries. Needs the international calling add-on. */
  canCallInternational: boolean | null;
  /** Needs the call recording add-on. */
  canRecord: boolean | null;
  /** Billing product code of the active base plan, e.g. "plan_standard". */
  planId: string | null;
  includedExtensions: number | null;
  currentPeriodStart: ISODate | null;
  currentPeriodEnd: ISODate | null;
  /** Shared included US minutes this period. */
  includedMinutesGranted: number | null;
  includedMinutesUsed: number | null;
  addons: Addon[] | null;
}

export type AccountStatus = "active" | "past_due";

/**
 * An add-on's state, as reported by Billing. Buy one with POST /v1/quotes and
 * POST /v1/purchases; remove or keep it with the /v1/subscriptions/items routes.
 */
export interface Addon {
  key: AddonKey;
  name: string;
  /** The add-on works right now. */
  enabled: boolean;
  /** Removed: keeps working until `worksUntil`, then is not renewed. */
  removing: boolean;
  worksUntil: ISODate | null;
  /** Price per 7-day period; null when Billing has no approved price. */
  priceCents: Cents | null;
  /** False when the add-on can't be bought right now. */
  priceAvailable: boolean;
}

export type AddonKey = "international_calling" | "call_recording";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
POST/api/v1/auth/loginauth.login

Exchange an email and password for an access token (24 hours) and a refresh token.

Request body
LoginRequest
Response
AuthResponse
Type definitions (11)
export interface LoginRequest {
  email: string;
  password: string;
}

export interface AuthResponse {
  user: User;
  account: Account;
  /** RS256 JWT. Send as `Authorization: Bearer <accessToken>` to Accounts and Billing. */
  accessToken: string;
  expiresIn: number;
  /** Single use: POST /api/v1/auth/refresh returns a new one. */
  refreshToken: string;
  refreshExpiresIn: number;
  tokenType: "Bearer";
  /** Only on registration: `pending_payment` if the plan charge couldn't be confirmed yet. */
  planStatus?: "active" | "pending_payment" | null;
}

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

/**
 * The business account. Identity fields come from Accounts; the rest come live
 * from Billing and are all null when Billing can't be reached (`billingAvailable: false`).
 */
export interface Account {
  id: ID;
  businessName: string;
  /** IANA zone, fixed after signup. Governs billing periods and business hours. */
  timeZone: string;
  /** SIP domain phones register at. */
  sipDomain: string;
  createdAt: ISODate;
  billingAvailable: boolean;
  status: AccountStatus | null;
  /** `pending` until the base plan is paid; no calling until `active`. */
  activationState: "pending" | "active" | null;
  /** `blocked` when outgoing calls are blocked. */
  restriction: "active" | "blocked" | null;
  canCallOutgoing: boolean | null;
  /** Calls outside the US, including other +1 countries. Needs the international calling add-on. */
  canCallInternational: boolean | null;
  /** Needs the call recording add-on. */
  canRecord: boolean | null;
  /** Billing product code of the active base plan, e.g. "plan_standard". */
  planId: string | null;
  includedExtensions: number | null;
  currentPeriodStart: ISODate | null;
  currentPeriodEnd: ISODate | null;
  /** Shared included US minutes this period. */
  includedMinutesGranted: number | null;
  includedMinutesUsed: number | null;
  addons: Addon[] | null;
}

export type AccountStatus = "active" | "past_due";

/**
 * An add-on's state, as reported by Billing. Buy one with POST /v1/quotes and
 * POST /v1/purchases; remove or keep it with the /v1/subscriptions/items routes.
 */
export interface Addon {
  key: AddonKey;
  name: string;
  /** The add-on works right now. */
  enabled: boolean;
  /** Removed: keeps working until `worksUntil`, then is not renewed. */
  removing: boolean;
  worksUntil: ISODate | null;
  /** Price per 7-day period; null when Billing has no approved price. */
  priceCents: Cents | null;
  /** False when the add-on can't be bought right now. */
  priceAvailable: boolean;
}

export type AddonKey = "international_calling" | "call_recording";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
Example request
POST /api/v1/auth/login
Content-Type: application/json

{
  "email": "jane@example.com",
  "password": "password123"
}
POST/api/v1/auth/refreshauth.refresh

Trade a refresh token for a new access token and a new refresh token. The old refresh token stops working; reusing one signs the user out everywhere.

Request body
RefreshRequest
Response
AuthResponse
Type definitions (11)
export interface RefreshRequest {
  refreshToken: string;
}

export interface AuthResponse {
  user: User;
  account: Account;
  /** RS256 JWT. Send as `Authorization: Bearer <accessToken>` to Accounts and Billing. */
  accessToken: string;
  expiresIn: number;
  /** Single use: POST /api/v1/auth/refresh returns a new one. */
  refreshToken: string;
  refreshExpiresIn: number;
  tokenType: "Bearer";
  /** Only on registration: `pending_payment` if the plan charge couldn't be confirmed yet. */
  planStatus?: "active" | "pending_payment" | null;
}

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

/**
 * The business account. Identity fields come from Accounts; the rest come live
 * from Billing and are all null when Billing can't be reached (`billingAvailable: false`).
 */
export interface Account {
  id: ID;
  businessName: string;
  /** IANA zone, fixed after signup. Governs billing periods and business hours. */
  timeZone: string;
  /** SIP domain phones register at. */
  sipDomain: string;
  createdAt: ISODate;
  billingAvailable: boolean;
  status: AccountStatus | null;
  /** `pending` until the base plan is paid; no calling until `active`. */
  activationState: "pending" | "active" | null;
  /** `blocked` when outgoing calls are blocked. */
  restriction: "active" | "blocked" | null;
  canCallOutgoing: boolean | null;
  /** Calls outside the US, including other +1 countries. Needs the international calling add-on. */
  canCallInternational: boolean | null;
  /** Needs the call recording add-on. */
  canRecord: boolean | null;
  /** Billing product code of the active base plan, e.g. "plan_standard". */
  planId: string | null;
  includedExtensions: number | null;
  currentPeriodStart: ISODate | null;
  currentPeriodEnd: ISODate | null;
  /** Shared included US minutes this period. */
  includedMinutesGranted: number | null;
  includedMinutesUsed: number | null;
  addons: Addon[] | null;
}

export type AccountStatus = "active" | "past_due";

/**
 * An add-on's state, as reported by Billing. Buy one with POST /v1/quotes and
 * POST /v1/purchases; remove or keep it with the /v1/subscriptions/items routes.
 */
export interface Addon {
  key: AddonKey;
  name: string;
  /** The add-on works right now. */
  enabled: boolean;
  /** Removed: keeps working until `worksUntil`, then is not renewed. */
  removing: boolean;
  worksUntil: ISODate | null;
  /** Price per 7-day period; null when Billing has no approved price. */
  priceCents: Cents | null;
  /** False when the add-on can't be bought right now. */
  priceAvailable: boolean;
}

export type AddonKey = "international_calling" | "call_recording";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
POST/api/v1/auth/logout๐Ÿ”’ Bearer tokenauth.logout

Revoke the given refresh token, or every refresh token for this user if none is given. Access tokens stay valid until they expire, so discard yours.

Request body
LogoutRequest
Response
Ok
Type definitions (2)
export interface LogoutRequest {
  /** Revoke just this session. Omit to sign out everywhere. */
  refreshToken?: string;
}

export interface Ok {
  ok: true;
}
GET/api/v1/auth/me๐Ÿ”’ Bearer tokenauth.me

Return the signed-in user.

Response
User
Type definitions (4)
export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PATCH/api/v1/auth/me๐Ÿ”’ Bearer tokenauth.updateMe

Change your own name or email.

Request body
UpdateMeRequest
Response
User
Type definitions (5)
export interface UpdateMeRequest {
  name?: string;
  email?: string;
}

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/auth/me/password๐Ÿ”’ Bearer tokenauth.changePassword

Change your password. Signs out every other session.

Request body
ChangePasswordRequest
Response
Ok
Type definitions (2)
export interface ChangePasswordRequest {
  currentPassword: string;
  newPassword: string;
}

export interface Ok {
  ok: true;
}
GET/api/v1/account๐Ÿ”’ Bearer tokenaccount.get

Return the account. Billing fields (status, plan, current period, included minutes, calling permissions, add-ons) come live from Billing and are null when Billing can't be reached (`billingAvailable: false`).

Response
Account
Type definitions (7)
/**
 * The business account. Identity fields come from Accounts; the rest come live
 * from Billing and are all null when Billing can't be reached (`billingAvailable: false`).
 */
export interface Account {
  id: ID;
  businessName: string;
  /** IANA zone, fixed after signup. Governs billing periods and business hours. */
  timeZone: string;
  /** SIP domain phones register at. */
  sipDomain: string;
  createdAt: ISODate;
  billingAvailable: boolean;
  status: AccountStatus | null;
  /** `pending` until the base plan is paid; no calling until `active`. */
  activationState: "pending" | "active" | null;
  /** `blocked` when outgoing calls are blocked. */
  restriction: "active" | "blocked" | null;
  canCallOutgoing: boolean | null;
  /** Calls outside the US, including other +1 countries. Needs the international calling add-on. */
  canCallInternational: boolean | null;
  /** Needs the call recording add-on. */
  canRecord: boolean | null;
  /** Billing product code of the active base plan, e.g. "plan_standard". */
  planId: string | null;
  includedExtensions: number | null;
  currentPeriodStart: ISODate | null;
  currentPeriodEnd: ISODate | null;
  /** Shared included US minutes this period. */
  includedMinutesGranted: number | null;
  includedMinutesUsed: number | null;
  addons: Addon[] | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type AccountStatus = "active" | "past_due";

/**
 * An add-on's state, as reported by Billing. Buy one with POST /v1/quotes and
 * POST /v1/purchases; remove or keep it with the /v1/subscriptions/items routes.
 */
export interface Addon {
  key: AddonKey;
  name: string;
  /** The add-on works right now. */
  enabled: boolean;
  /** Removed: keeps working until `worksUntil`, then is not renewed. */
  removing: boolean;
  worksUntil: ISODate | null;
  /** Price per 7-day period; null when Billing has no approved price. */
  priceCents: Cents | null;
  /** False when the add-on can't be bought right now. */
  priceAvailable: boolean;
}

export type AddonKey = "international_calling" | "call_recording";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
PATCH/api/v1/account๐Ÿ”’ Bearer tokenaccount.update

Change the business name. Admins only. The time zone can't be changed.

Request body
UpdateAccountRequest
Response
Account
Type definitions (8)
export interface UpdateAccountRequest {
  businessName?: string;
}

/**
 * The business account. Identity fields come from Accounts; the rest come live
 * from Billing and are all null when Billing can't be reached (`billingAvailable: false`).
 */
export interface Account {
  id: ID;
  businessName: string;
  /** IANA zone, fixed after signup. Governs billing periods and business hours. */
  timeZone: string;
  /** SIP domain phones register at. */
  sipDomain: string;
  createdAt: ISODate;
  billingAvailable: boolean;
  status: AccountStatus | null;
  /** `pending` until the base plan is paid; no calling until `active`. */
  activationState: "pending" | "active" | null;
  /** `blocked` when outgoing calls are blocked. */
  restriction: "active" | "blocked" | null;
  canCallOutgoing: boolean | null;
  /** Calls outside the US, including other +1 countries. Needs the international calling add-on. */
  canCallInternational: boolean | null;
  /** Needs the call recording add-on. */
  canRecord: boolean | null;
  /** Billing product code of the active base plan, e.g. "plan_standard". */
  planId: string | null;
  includedExtensions: number | null;
  currentPeriodStart: ISODate | null;
  currentPeriodEnd: ISODate | null;
  /** Shared included US minutes this period. */
  includedMinutesGranted: number | null;
  includedMinutesUsed: number | null;
  addons: Addon[] | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type AccountStatus = "active" | "past_due";

/**
 * An add-on's state, as reported by Billing. Buy one with POST /v1/quotes and
 * POST /v1/purchases; remove or keep it with the /v1/subscriptions/items routes.
 */
export interface Addon {
  key: AddonKey;
  name: string;
  /** The add-on works right now. */
  enabled: boolean;
  /** Removed: keeps working until `worksUntil`, then is not renewed. */
  removing: boolean;
  worksUntil: ISODate | null;
  /** Price per 7-day period; null when Billing has no approved price. */
  priceCents: Cents | null;
  /** False when the add-on can't be bought right now. */
  priceAvailable: boolean;
}

export type AddonKey = "international_calling" | "call_recording";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
GET/api/v1/users๐Ÿ”’ Bearer tokenusers.list

List the account's users, oldest first.

Response
User[]
Type definitions (4)
export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/api/v1/users/{id}๐Ÿ”’ Bearer tokenusers.get

Return one user in the account.

Response
User
Type definitions (4)
export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

export type UserRole = "admin" | "user";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/users๐Ÿ”’ Bearer tokenusers.create

Add a user to the account. Admins only.

Request body
CreateUserRequest
Response
User
Type definitions (5)
export interface CreateUserRequest {
  name: string;
  email: string;
  password: string;
  /** Defaults to `user`. */
  role?: UserRole;
}

export type UserRole = "admin" | "user";

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PATCH/api/v1/users/{id}๐Ÿ”’ Bearer tokenusers.update

Change a user's name or role. Admins only. The account must keep at least one admin.

Request body
UpdateUserRequest
Response
User
Type definitions (5)
export interface UpdateUserRequest {
  name?: string;
  role?: UserRole;
}

export type UserRole = "admin" | "user";

export interface User {
  id: ID;
  accountId: ID;
  email: string;
  name: string;
  role: UserRole;
  createdAt: ISODate;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/users/{id}๐Ÿ”’ Bearer tokenusers.delete

Remove a user from the account and sign them out. Admins only. You can't remove yourself.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
POST/api/v1/provider/loginprovider.login

Provider staff login, separate from customer accounts. The token only works on provider endpoints.

Request body
LoginRequest
Response
ProviderLoginResponse
Type definitions (2)
export interface LoginRequest {
  email: string;
  password: string;
}

export interface ProviderLoginResponse {
  accessToken: string;
  email: string;
  expiresIn: number;
}

numbers

The Numbers service: searches the DID provider, holds a number while you buy it through Billing, routes the account's numbers, and sets the caller ID.

GET/api/v1/numbers๐Ÿ”’ Bearer tokennumbers.list

List the account's phone numbers, sorted, including `releasing` ones and `pending` orders: a number bought through Billing (POST /v1/purchases with `did_e164`) is listed as `pending` right away, until the provider has set it up.

Query
ListNumbersQuery
Response
PhoneNumber[]
Type definitions (9)
export interface ListNumbersQuery {
  /** 1 to 1000; default 100. */
  limit?: number;
  offset?: number;
}

/** A number the account owns. */
export interface PhoneNumber extends AvailableNumber {
  status: NumberStatus;
  /** Where its calls go; null while unrouted. */
  destination: NumberDestination | null;
  /** End of the paid period. Null while pending. */
  paid_until: ISODate | null;
}

/** A number the provider can sell. Not a reservation. */
export interface AvailableNumber {
  number: E164;
  type: NumberType;
  /** Only "US" for now. */
  country: string;
  /** Two-letter state code; null for toll-free. */
  region: string | null;
  /** Null for toll-free. */
  city: string | null;
  /** Area code, or the 8XX prefix for toll-free. */
  area_code: string;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type NumberType = "local" | "toll_free";

/** `pending`: the provider is still processing the order. `releasing`: released, works until `paid_until`. */
export type NumberStatus = "pending" | "active" | "releasing";

/** Where Numbers sends a number's calls; Calling resolves `id` in the account's call handling. */
export interface NumberDestination {
  type: "extension" | "ring_group" | "ivr" | "schedule";
  id: ID;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/api/v1/numbers/available๐Ÿ”’ Bearer tokennumbers.search

Search the DID provider for numbers to buy: type, country (US only), two-letter state code, city, area code. Offers, not reservations. Prices are in GET /v1/catalog; buy with POST /v1/quotes then POST /v1/purchases with `did_e164`.

Query
SearchNumbersQuery
Response
AvailableNumber[]
Type definitions (4)
export interface SearchNumbersQuery {
  type?: NumberType;
  /** Only "US" for now. */
  country?: string;
  /** Two-letter state code, e.g. "NY". Ignored for toll-free. */
  region?: string;
  /** Case-insensitive. Ignored for toll-free. */
  city?: string;
  /** Three digits, or the 8XX prefix for toll-free. */
  area_code?: string;
  /** 1 to 50; default 5. */
  limit?: number;
}

export type NumberType = "local" | "toll_free";

/** A number the provider can sell. Not a reservation. */
export interface AvailableNumber {
  number: E164;
  type: NumberType;
  /** Only "US" for now. */
  country: string;
  /** Two-letter state code; null for toll-free. */
  region: string | null;
  /** Null for toll-free. */
  city: string | null;
  /** Area code, or the 8XX prefix for toll-free. */
  area_code: string;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;
Example request
GET /api/v1/numbers/available?area_code=631&type=local&limit=2
GET/api/v1/numbers/{number}๐Ÿ”’ Bearer tokennumbers.get

One of the account's numbers, re-checked with the provider while its order is pending. A number just bought through Billing is returned as `pending` right away.

Response
PhoneNumber
Type definitions (8)
/** A number the account owns. */
export interface PhoneNumber extends AvailableNumber {
  status: NumberStatus;
  /** Where its calls go; null while unrouted. */
  destination: NumberDestination | null;
  /** End of the paid period. Null while pending. */
  paid_until: ISODate | null;
}

/** A number the provider can sell. Not a reservation. */
export interface AvailableNumber {
  number: E164;
  type: NumberType;
  /** Only "US" for now. */
  country: string;
  /** Two-letter state code; null for toll-free. */
  region: string | null;
  /** Null for toll-free. */
  city: string | null;
  /** Area code, or the 8XX prefix for toll-free. */
  area_code: string;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type NumberType = "local" | "toll_free";

/** `pending`: the provider is still processing the order. `releasing`: released, works until `paid_until`. */
export type NumberStatus = "pending" | "active" | "releasing";

/** Where Numbers sends a number's calls; Calling resolves `id` in the account's call handling. */
export interface NumberDestination {
  type: "extension" | "ring_group" | "ivr" | "schedule";
  id: ID;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/numbers/{number}/reservation๐Ÿ”’ Bearer tokennumbers.reserve

Hold a found number for about 30 minutes while the account buys it through Billing; nobody else can buy it meanwhile. One hold per account: a new one releases the last. 404 when it's no longer for sale.

Response
NumberReservation
Type definitions (3)
/** A found number held for the account while it buys it through Billing (about 30 minutes). */
export interface NumberReservation {
  number: E164;
  expires_at: ISODate;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PUT/api/v1/numbers/{number}/destination๐Ÿ”’ Bearer tokennumbers.setDestination

Set where calls to a number go: an extension, ring group, IVR menu, or business-hours schedule (`{type, id}`). Works while the number is pending.

Request body
NumberDestination
Response
PhoneNumber
Type definitions (8)
/** Where Numbers sends a number's calls; Calling resolves `id` in the account's call handling. */
export interface NumberDestination {
  type: "extension" | "ring_group" | "ivr" | "schedule";
  id: ID;
}

export type ID = string;

/** A number the account owns. */
export interface PhoneNumber extends AvailableNumber {
  status: NumberStatus;
  /** Where its calls go; null while unrouted. */
  destination: NumberDestination | null;
  /** End of the paid period. Null while pending. */
  paid_until: ISODate | null;
}

/** A number the provider can sell. Not a reservation. */
export interface AvailableNumber {
  number: E164;
  type: NumberType;
  /** Only "US" for now. */
  country: string;
  /** Two-letter state code; null for toll-free. */
  region: string | null;
  /** Null for toll-free. */
  city: string | null;
  /** Area code, or the 8XX prefix for toll-free. */
  area_code: string;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type NumberType = "local" | "toll_free";

/** `pending`: the provider is still processing the order. `releasing`: released, works until `paid_until`. */
export type NumberStatus = "pending" | "active" | "releasing";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/numbers/{number}/destination๐Ÿ”’ Bearer tokennumbers.clearDestination

Unroute a number: its calls go nowhere.

Response
PhoneNumber
Type definitions (8)
/** A number the account owns. */
export interface PhoneNumber extends AvailableNumber {
  status: NumberStatus;
  /** Where its calls go; null while unrouted. */
  destination: NumberDestination | null;
  /** End of the paid period. Null while pending. */
  paid_until: ISODate | null;
}

/** A number the provider can sell. Not a reservation. */
export interface AvailableNumber {
  number: E164;
  type: NumberType;
  /** Only "US" for now. */
  country: string;
  /** Two-letter state code; null for toll-free. */
  region: string | null;
  /** Null for toll-free. */
  city: string | null;
  /** Area code, or the 8XX prefix for toll-free. */
  area_code: string;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type NumberType = "local" | "toll_free";

/** `pending`: the provider is still processing the order. `releasing`: released, works until `paid_until`. */
export type NumberStatus = "pending" | "active" | "releasing";

/** Where Numbers sends a number's calls; Calling resolves `id` in the account's call handling. */
export interface NumberDestination {
  type: "extension" | "ring_group" | "ivr" | "schedule";
  id: ID;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/api/v1/numbers/caller-id๐Ÿ”’ Bearer tokennumbers.callerId

The number shown on outgoing calls: the chosen one while usable, else the account's first usable number. 404 when the account has none.

Response
CallerId
Type definitions (3)
/** The number shown on the account's outgoing calls. */
export interface CallerId {
  number: E164;
  account_id: ID;
  /** False while the chosen number is still pending. */
  usable: boolean;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type ID = string;
PUT/api/v1/numbers/caller-id๐Ÿ”’ Bearer tokennumbers.setCallerId

Choose which of the account's numbers outgoing calls show. It applies once the number is usable.

Request body
SetCallerIdRequest
Response
CallerId
Type definitions (4)
export interface SetCallerIdRequest {
  number: E164;
}

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** The number shown on the account's outgoing calls. */
export interface CallerId {
  number: E164;
  account_id: ID;
  /** False while the chosen number is still pending. */
  usable: boolean;
}

export type ID = string;

billing

The Billing service (/v1): the price catalog, quotes and purchases, recurring items, the card (through Stripe.js SetupIntents), invoices, and payments.

GET/v1/catalogbilling.catalog

Every approved price, no sign-in needed: products (plans, extension, numbers, add-ons) per 7-day period, and per-minute rates by destination prefix (US, international, incoming toll-free) with the destination's name. The plans (`kind: plan`) are the valid `planId` values for signup, e.g. `plan_standard`. Unavailable entries stay listed with `available: false` and must not be bought.

Response
Catalog
Type definitions (8)
export interface Catalog {
  catalog_version: string | null;
  catalog_version_id: ID | null;
  products: CatalogProduct[];
  rates: CatalogRate[];
}

export type ID = string;

export interface CatalogProduct {
  product_id: ID;
  /** Stable code, e.g. "plan_standard" or "call_recording". */
  code: string;
  kind: ProductKind;
  catalog_version_id: ID;
  /** Price per `unit`, decimal USD. */
  amount: string;
  currency: string;
  /** Always "week": recurring prices are per 7-day period. */
  unit: string;
  /** Unavailable entries must not be bought. */
  available: boolean;
  unavailable_reason: string | null;
  /** Plans: `included_quantity` (extensions). Numbers: `number_type`. Add-ons: `addon_code`. */
  product_details: Record<string, unknown>;
  /** Plans: `included_us_seconds`. */
  price_details: Record<string, unknown>;
}

export type ProductKind = "plan" | "extension" | "number" | "addon";

export interface CatalogRate {
  kind: RateKind;
  /** Destination prefix with "+", e.g. "+44". The longest matching prefix wins. */
  prefix: string;
  /** Matched when rating: empty, or `US` for incoming toll-free. */
  region: string;
  /** Destination name, e.g. "United Kingdom". */
  destination: string;
  /** Decimal USD per minute. */
  amount_per_minute: string;
  available: boolean;
  unavailable_reason: string | null;
}

export type RateKind = "us_overage" | "international" | "toll_free";

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;
POST/v1/quotes๐Ÿ”’ Bearer tokenbilling.quote

Price a product now: prorated to the end of the current period, with its included allowance. Doesn't reserve or charge anything.

Request body
QuoteRequest
Response
Quote
Type definitions (6)
export interface QuoteRequest {
  product_id: ID;
  /** 1 to 100; default 1. Plans, numbers and add-ons must be 1. */
  quantity?: number;
  /** Numbers only: the E.164 number to buy. */
  did_e164?: E164 | null;
}

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** A point-in-time prorated price. Doesn't reserve or charge anything. */
export interface Quote {
  catalog_version: string;
  catalog_version_id: ID;
  product_id: ID;
  product_code: string;
  product_kind: ProductKind;
  product_details: Record<string, unknown>;
  price_details: Record<string, unknown>;
  quantity: number;
  did_e164: E164 | null;
  period_start: ISODate;
  period_end: ISODate;
  scheduled_start: ISODate;
  /** Share of the period left, as a decimal string. */
  fraction: string;
  weekly_price_id: ID;
  weekly_amount: string;
  weekly_cents: number;
  setup_price_id: ID | null;
  setup_amount: string | null;
  setup_cents: number;
  /** What the purchase will charge now, in cents. */
  total_cents: number;
  full_included_us_seconds: number;
  included_us_seconds: number;
  initial_included_us_seconds: number;
  per_unit_included_us_seconds: number;
}

export type ProductKind = "plan" | "extension" | "number" | "addon";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/v1/purchases๐Ÿ”’ Bearer tokenbilling.purchase

Buy a product at a quoted price, charged to the account's card now. Admins only. Send the quote's `catalog_version_id` and `total_cents`; a stale quote returns 409. The item activates once payment (and, for numbers, provisioning) succeeds.

Request body
PurchaseRequest
Response
Purchase
Type definitions (5)
export interface PurchaseRequest extends QuoteRequest {
  /** From the quote. A stale version returns 409: quote again. */
  catalog_version_id: ID;
  /** The quote's `total_cents`. A changed amount returns 409. */
  expected_total_cents: number;
}

export interface QuoteRequest {
  product_id: ID;
  /** 1 to 100; default 1. Plans, numbers and add-ons must be 1. */
  quantity?: number;
  /** Numbers only: the E.164 number to buy. */
  did_e164?: E164 | null;
}

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** Order status: `pending_payment`, `active`, `canceled`, or a provisioning state for numbers. */
export interface Purchase {
  id: ID;
  invoice_id: ID;
  status: string;
}
GET/v1/purchases๐Ÿ”’ Bearer tokenbilling.purchases

List the account's purchase orders, newest first.

Response
PurchaseListItem[]
Type definitions (3)
export interface PurchaseListItem extends Purchase {
  price_snapshot: Record<string, unknown>;
}

/** Order status: `pending_payment`, `active`, `canceled`, or a provisioning state for numbers. */
export interface Purchase {
  id: ID;
  invoice_id: ID;
  status: string;
}

export type ID = string;
GET/v1/purchases/{id}๐Ÿ”’ Bearer tokenbilling.purchaseStatus

One purchase order with its invoice status and, for numbers, provisioning status.

Response
PurchaseStatus
Type definitions (5)
export interface PurchaseStatus extends PurchaseListItem {
  invoice_status: InvoiceStatus;
  /** Numbers only: provisioning state at the DID provider. */
  number_status: string | null;
}

export interface PurchaseListItem extends Purchase {
  price_snapshot: Record<string, unknown>;
}

/** Order status: `pending_payment`, `active`, `canceled`, or a provisioning state for numbers. */
export interface Purchase {
  id: ID;
  invoice_id: ID;
  status: string;
}

export type ID = string;

export type InvoiceStatus = "draft" | "open" | "paid" | "past_due" | "void";
GET/v1/subscriptions๐Ÿ”’ Bearer tokenbilling.subscriptions

The account's recurring items (plan, extensions, numbers, add-ons) with paid-through and removal times. A number's item names the number in `did_e164`: to release a number, find its item here, then POST /v1/subscriptions/items/{id}/remove.

Response
SubscriptionItem[]
Type definitions (4)
export interface SubscriptionItem {
  item_id: ID;
  product_id: ID;
  quantity: number;
  /** `pending`, `active`, `removing`, or `removed`. */
  status: string;
  active_from: ISODate | null;
  paid_through: ISODate | null;
  /** Set once removed: the item stops at this time and isn't renewed. */
  removal_at: ISODate | null;
  entitlement: string | null;
  /** Numbers only: which number this item pays for. Remove the item to release the number. */
  did_e164: E164 | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;
POST/v1/subscriptions/items/{id}/remove๐Ÿ”’ Bearer tokenbilling.removeItem

Remove a recurring item. Admins only. It keeps working until its paid period ends and is not renewed. This is also how a number is released: remove the item whose `did_e164` is the number (GET /v1/subscriptions).

Response
Removal
Type definitions (3)
export interface Removal {
  item_id: ID;
  status: string;
  removal_at: ISODate | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/v1/subscriptions/items/{id}/resume๐Ÿ”’ Bearer tokenbilling.resumeItem

Keep a removed add-on before its paid period ends, at no charge. Admins only.

Response
Removal
Type definitions (3)
export interface Removal {
  item_id: ID;
  status: string;
  removal_at: ISODate | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/v1/card/setup๐Ÿ”’ Bearer tokenbilling.cardSetup

Start replacing the account's card. Admins only. Returns a SetupIntent `client_secret` for Stripe.js: call `stripe.confirmCardSetup(client_secret, ...)` in the browser, then send `setup_intent_id` to POST /v1/card/complete.

Response
CardSetup
Type definitions (2)
/** Pass `client_secret` to Stripe.js `confirmCardSetup`, then send `setup_intent_id` to POST /v1/card/complete. */
export interface CardSetup {
  setup_intent_id: ID | null;
  client_secret: string | null;
  publishable_key: string | null;
}

export type ID = string;
POST/v1/card/complete๐Ÿ”’ Bearer tokenbilling.cardComplete

Make the card from a confirmed SetupIntent the account's card. Admins only. Overdue invoices are retried on it right away.

Request body
CompleteCardRequest
Response
Card
Type definitions (3)
export interface CompleteCardRequest {
  setup_intent_id: ID;
}

export type ID = string;

/** The account's card. Only safe metadata is ever available. */
export interface Card {
  id: ID;
  brand: string | null;
  last4: string | null;
  exp_month: number | null;
  exp_year: number | null;
}
GET/v1/card/default๐Ÿ”’ Bearer tokenbilling.card

The account's card: brand, last 4 digits and expiry, or null.

Response
Card | null
Type definitions (2)
/** The account's card. Only safe metadata is ever available. */
export interface Card {
  id: ID;
  brand: string | null;
  last4: string | null;
  exp_month: number | null;
  exp_year: number | null;
}

export type ID = string;
GET/v1/billing-details๐Ÿ”’ Bearer tokenbilling.details

The name and address shown on invoices, or null.

Response
BillingDetails | null
Type definitions (1)
/** Name and address shown on invoices. */
export interface BillingDetails {
  name: string;
  email: string;
  company?: string | null;
  address_line1: string;
  address_line2?: string | null;
  city: string;
  region?: string | null;
  postal_code: string;
  country: string;
}
PUT/v1/billing-details๐Ÿ”’ Bearer tokenbilling.updateDetails

Replace the name and address shown on invoices. Admins only.

Request body
BillingDetails
Response
BillingDetails
Type definitions (1)
/** Name and address shown on invoices. */
export interface BillingDetails {
  name: string;
  email: string;
  company?: string | null;
  address_line1: string;
  address_line2?: string | null;
  city: string;
  region?: string | null;
  postal_code: string;
  country: string;
}
GET/v1/invoices๐Ÿ”’ Bearer tokenbilling.invoices

List invoices, newest first.

Query
InvoicesQuery
Response
InvoiceSummary[]
Type definitions (6)
export interface InvoicesQuery {
  /** 1 to 100; default 25. */
  limit?: number;
  offset?: number;
}

export interface InvoiceSummary {
  id: ID;
  kind: InvoiceKind;
  status: InvoiceStatus;
  currency: string;
  total_cents: number;
  period_id: ID | null;
  /** The billing period the invoice is for. */
  period_start: ISODate | null;
  period_end: ISODate | null;
  issued_at: ISODate | null;
  due_at: ISODate | null;
  paid_at: ISODate | null;
}

export type ID = string;

/** `purchase` is a mid-period charge; `renewal` is the 7-day invoice. */
export type InvoiceKind = "purchase" | "renewal";

export type InvoiceStatus = "draft" | "open" | "paid" | "past_due" | "void";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/v1/invoices/{id}๐Ÿ”’ Bearer tokenbilling.invoice

One invoice, itemized, with the included US allowance used.

Response
InvoiceDetail
Type definitions (8)
export interface InvoiceDetail extends InvoiceSummary {
  lines: InvoiceLine[];
  allowance: InvoiceAllowance;
}

export interface InvoiceSummary {
  id: ID;
  kind: InvoiceKind;
  status: InvoiceStatus;
  currency: string;
  total_cents: number;
  period_id: ID | null;
  /** The billing period the invoice is for. */
  period_start: ISODate | null;
  period_end: ISODate | null;
  issued_at: ISODate | null;
  due_at: ISODate | null;
  paid_at: ISODate | null;
}

export type ID = string;

/** `purchase` is a mid-period charge; `renewal` is the 7-day invoice. */
export type InvoiceKind = "purchase" | "renewal";

export type InvoiceStatus = "draft" | "open" | "paid" | "past_due" | "void";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export interface InvoiceLine {
  id: ID;
  /** `recurring`, `setup`, or `usage`. */
  kind: string;
  description: string;
  /** Decimal string. */
  quantity: string;
  unit_amount: string;
  amount_cents: number;
  source_type: string | null;
  source_id: ID | null;
  price_snapshot: Record<string, unknown>;
}

/** Included US allowance for the period an invoice's usage belongs to. */
export interface InvoiceAllowance {
  period_id?: ID;
  granted_seconds?: number;
  used_seconds?: number;
}
GET/v1/transactions๐Ÿ”’ Bearer tokenbilling.transactions

List settled payments, newest first.

Query
InvoicesQuery
Response
Transaction[]
Type definitions (4)
export interface InvoicesQuery {
  /** 1 to 100; default 25. */
  limit?: number;
  offset?: number;
}

/** A settled payment. */
export interface Transaction {
  id: ID;
  invoice_id: ID;
  kind: string;
  amount_cents: number;
  currency: string;
  settled_at: ISODate;
  gateway_object_id: string | null;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

extensions

The Call Manager: extensions, SIP credentials, and forwarding; extra extensions are bought through Billing.

GET/api/v1/extensions๐Ÿ”’ Bearer tokenextensions.list

List extensions with their SIP username and password, forwarding and simultaneous-ring numbers. Admins only.

Response
Extension[]
Type definitions (10)
export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

export type ID = string;

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";
POST/api/v1/extensions๐Ÿ”’ Bearer tokenextensions.create

Create an extension; SIP credentials are generated and returned. When the plan's extensions are used up it fails with 409 `extension_capacity_exhausted`: get a quote and send it back as `purchase` to buy one. Replay the same body and Idempotency-Key to finish a pending purchase.

Request body
CreateExtensionRequest
Response
Extension
Type definitions (12)
export interface CreateExtensionRequest {
  number: string;
  name: string;
  forwarding?: Forwarding;
  simultaneousRing?: E164[];
  /** Only when included extensions are used up (409 `extension_capacity_exhausted`). */
  purchase?: AcceptedExtensionPurchase;
}

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** A quote the admin accepted, to buy one extra extension when the plan's are used up. */
export interface AcceptedExtensionPurchase {
  productId: ID;
  catalogVersionId: ID;
  expectedTotalCents: number;
}

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";
POST/api/v1/extensions/quote๐Ÿ”’ Bearer tokenextensions.quote

Price one more extension now (prorated) and per week, without charging. Accept it in `extensions.create`'s `purchase`.

Request body
ExtensionQuoteRequest
Response
ExtensionQuote
Type definitions (4)
export interface ExtensionQuoteRequest {
  productId: ID;
}

export type ID = string;

export interface ExtensionQuote {
  productId: ID;
  productCode: string;
  catalogVersionId: ID;
  /** Charged now, prorated to the end of the period. */
  expectedTotalCents: number;
  weeklyCents: number;
  setupCents: number;
  periodEnd: ISODate;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PATCH/api/v1/extensions/{id}๐Ÿ”’ Bearer tokenextensions.update

Update an extension's number, name, forwarding, or simultaneous-ring numbers, or generate a new SIP password.

Request body
UpdateExtensionRequest
Response
Extension
Type definitions (11)
export interface UpdateExtensionRequest {
  number?: string;
  name?: string;
  forwarding?: Forwarding;
  simultaneousRing?: E164[];
  /** True generates a new SIP password. */
  regeneratePassword?: boolean;
}

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";
DELETE/api/v1/extensions/{id}๐Ÿ”’ Bearer tokenextensions.delete

Remove an included extension. It works until the paid period ends.

Response
Extension
Type definitions (10)
export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

export type ID = string;

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";

call-handling

The Call Manager: ring groups, business hours, IVR menus with their greetings, and the blocklist.

GET/api/v1/ring-groups๐Ÿ”’ Bearer tokenringGroups.list

List the account's ring groups.

Response
RingGroup[]
Type definitions (7)
export interface RingGroup {
  id: ID;
  accountId: ID;
  name: string;
  strategy: RingStrategy;
  members: RingGroupMember[];
  /** Simultaneous groups: how long everyone rings. */
  timeoutSeconds: number;
  noAnswerDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export type RingStrategy = "simultaneous" | "sequential";

export interface RingGroupMember {
  extensionId: ID;
  /** Sequential groups: how long this member rings before the next. */
  ringSeconds: number;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/ring-groups๐Ÿ”’ Bearer tokenringGroups.create

Create a ring group.

Request body
UpsertRingGroupRequest
Response
RingGroup
Type definitions (8)
export type UpsertRingGroupRequest = Pick<RingGroup, "name" | "strategy" | "members" | "timeoutSeconds" | "noAnswerDestination">;

export interface RingGroup {
  id: ID;
  accountId: ID;
  name: string;
  strategy: RingStrategy;
  members: RingGroupMember[];
  /** Simultaneous groups: how long everyone rings. */
  timeoutSeconds: number;
  noAnswerDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export type RingStrategy = "simultaneous" | "sequential";

export interface RingGroupMember {
  extensionId: ID;
  /** Sequential groups: how long this member rings before the next. */
  ringSeconds: number;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PUT/api/v1/ring-groups/{id}๐Ÿ”’ Bearer tokenringGroups.update

Replace a ring group.

Request body
UpsertRingGroupRequest
Response
RingGroup
Type definitions (8)
export type UpsertRingGroupRequest = Pick<RingGroup, "name" | "strategy" | "members" | "timeoutSeconds" | "noAnswerDestination">;

export interface RingGroup {
  id: ID;
  accountId: ID;
  name: string;
  strategy: RingStrategy;
  members: RingGroupMember[];
  /** Simultaneous groups: how long everyone rings. */
  timeoutSeconds: number;
  noAnswerDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export type RingStrategy = "simultaneous" | "sequential";

export interface RingGroupMember {
  extensionId: ID;
  /** Sequential groups: how long this member rings before the next. */
  ringSeconds: number;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/ring-groups/{id}๐Ÿ”’ Bearer tokenringGroups.delete

Delete a ring group. Numbers routed to it become unrouted, and other destinations pointing to it hang up.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
GET/api/v1/business-hours๐Ÿ”’ Bearer tokenhours.list

List the account's business-hours schedules.

Response
BusinessHours[]
Type definitions (6)
export interface BusinessHours {
  id: ID;
  accountId: ID;
  name: string;
  intervals: BusinessHoursInterval[];
  openDestination: Destination;
  closedDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export interface BusinessHoursInterval {
  /** 0 = Sunday. */
  day: number;
  /** "09:00" in the account's time zone. */
  open: string;
  close: string;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/business-hours๐Ÿ”’ Bearer tokenhours.create

Create a business-hours schedule.

Request body
UpsertBusinessHoursRequest
Response
BusinessHours
Type definitions (7)
export type UpsertBusinessHoursRequest = Pick<BusinessHours, "name" | "intervals" | "openDestination" | "closedDestination">;

export interface BusinessHours {
  id: ID;
  accountId: ID;
  name: string;
  intervals: BusinessHoursInterval[];
  openDestination: Destination;
  closedDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export interface BusinessHoursInterval {
  /** 0 = Sunday. */
  day: number;
  /** "09:00" in the account's time zone. */
  open: string;
  close: string;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
PUT/api/v1/business-hours/{id}๐Ÿ”’ Bearer tokenhours.update

Replace a business-hours schedule.

Request body
UpsertBusinessHoursRequest
Response
BusinessHours
Type definitions (7)
export type UpsertBusinessHoursRequest = Pick<BusinessHours, "name" | "intervals" | "openDestination" | "closedDestination">;

export interface BusinessHours {
  id: ID;
  accountId: ID;
  name: string;
  intervals: BusinessHoursInterval[];
  openDestination: Destination;
  closedDestination: Destination;
  updatedAt: ISODate;
}

export type ID = string;

export interface BusinessHoursInterval {
  /** 0 = Sunday. */
  day: number;
  /** "09:00" in the account's time zone. */
  open: string;
  close: string;
}

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/business-hours/{id}๐Ÿ”’ Bearer tokenhours.delete

Delete a business-hours schedule. Numbers routed to it become unrouted, and other destinations pointing to it hang up.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
GET/api/v1/ivr/menus๐Ÿ”’ Bearer tokenivr.list

List the account's IVR menus.

Response
IvrMenu[]
Type definitions (8)
export interface IvrMenu {
  id: ID;
  accountId: ID;
  name: string;
  /** Uploaded WAV (16-bit PCM mono 8 kHz); null until one is uploaded. */
  greeting: IvrGreeting | null;
  /** Seconds to wait for a digit before `timeoutAction`. */
  timeoutSeconds: number;
  timeoutAction: Destination;
  invalidInputAction: Destination;
  /** How many times `repeat` replays the menu before hanging up. */
  maxRepeats: number;
  options: IvrOption[];
  updatedAt: ISODate;
}

export type ID = string;

export interface IvrGreeting {
  fileName: string;
  durationSeconds: number;
  uploadedAt: ISODate;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface IvrOption {
  digit: DtmfKey;
  label: string;
  destination: Destination;
}

export type DtmfKey = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";
POST/api/v1/ivr/menus๐Ÿ”’ Bearer tokenivr.create

Create a IVR menu.

Request body
UpsertIvrMenuRequest
Response
IvrMenu
Type definitions (9)
export type UpsertIvrMenuRequest = Pick<IvrMenu, "name" | "timeoutSeconds" | "timeoutAction" | "invalidInputAction" | "maxRepeats" | "options">;

export interface IvrMenu {
  id: ID;
  accountId: ID;
  name: string;
  /** Uploaded WAV (16-bit PCM mono 8 kHz); null until one is uploaded. */
  greeting: IvrGreeting | null;
  /** Seconds to wait for a digit before `timeoutAction`. */
  timeoutSeconds: number;
  timeoutAction: Destination;
  invalidInputAction: Destination;
  /** How many times `repeat` replays the menu before hanging up. */
  maxRepeats: number;
  options: IvrOption[];
  updatedAt: ISODate;
}

export type ID = string;

export interface IvrGreeting {
  fileName: string;
  durationSeconds: number;
  uploadedAt: ISODate;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface IvrOption {
  digit: DtmfKey;
  label: string;
  destination: Destination;
}

export type DtmfKey = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";
PUT/api/v1/ivr/menus/{id}๐Ÿ”’ Bearer tokenivr.update

Replace a IVR menu.

Request body
UpsertIvrMenuRequest
Response
IvrMenu
Type definitions (9)
export type UpsertIvrMenuRequest = Pick<IvrMenu, "name" | "timeoutSeconds" | "timeoutAction" | "invalidInputAction" | "maxRepeats" | "options">;

export interface IvrMenu {
  id: ID;
  accountId: ID;
  name: string;
  /** Uploaded WAV (16-bit PCM mono 8 kHz); null until one is uploaded. */
  greeting: IvrGreeting | null;
  /** Seconds to wait for a digit before `timeoutAction`. */
  timeoutSeconds: number;
  timeoutAction: Destination;
  invalidInputAction: Destination;
  /** How many times `repeat` replays the menu before hanging up. */
  maxRepeats: number;
  options: IvrOption[];
  updatedAt: ISODate;
}

export type ID = string;

export interface IvrGreeting {
  fileName: string;
  durationSeconds: number;
  uploadedAt: ISODate;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface IvrOption {
  digit: DtmfKey;
  label: string;
  destination: Destination;
}

export type DtmfKey = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";
DELETE/api/v1/ivr/menus/{id}๐Ÿ”’ Bearer tokenivr.delete

Delete a IVR menu. Numbers routed to it become unrouted, and other destinations pointing to it hang up.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
PUT/api/v1/ivr/menus/{id}/greeting๐Ÿ”’ Bearer tokenivr.uploadGreeting

Upload a menu's greeting. The request body is a WAV file: 16-bit PCM, mono, 8 kHz. Other formats are rejected with 400.

Request body
UploadIvrGreetingRequest
Response
IvrMenu
Type definitions (9)
export interface UploadIvrGreetingRequest {
  fileName: string;
  /** Raw WAV bytes, sent as the `audio/wav` request body. */
  wav: ArrayBuffer;
}

export interface IvrMenu {
  id: ID;
  accountId: ID;
  name: string;
  /** Uploaded WAV (16-bit PCM mono 8 kHz); null until one is uploaded. */
  greeting: IvrGreeting | null;
  /** Seconds to wait for a digit before `timeoutAction`. */
  timeoutSeconds: number;
  timeoutAction: Destination;
  invalidInputAction: Destination;
  /** How many times `repeat` replays the menu before hanging up. */
  maxRepeats: number;
  options: IvrOption[];
  updatedAt: ISODate;
}

export type ID = string;

export interface IvrGreeting {
  fileName: string;
  durationSeconds: number;
  uploadedAt: ISODate;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Destination =
  | { type: "extension"; extensionId: ID }
  | { type: "ring_group"; ringGroupId: ID }
  | { type: "ivr"; ivrMenuId: ID }
  | { type: "voicemail"; extensionId: ID }
  | { type: "external"; number: E164 }
  | { type: "schedule"; businessHoursId: ID }
  | { type: "repeat" }
  | { type: "hangup" };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface IvrOption {
  digit: DtmfKey;
  label: string;
  destination: Destination;
}

export type DtmfKey = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";
GET/api/v1/ivr/menus/{id}/greeting๐Ÿ”’ Bearer tokenivr.greetingAudio

Download a menu's greeting as WAV (16-bit PCM mono, 8 kHz).

Response
WAV
GET/api/v1/blocklist๐Ÿ”’ Bearer tokenblocklist.list

List blocked callers.

Response
BlockedCaller[]
Type definitions (4)
export interface BlockedCaller {
  id: ID;
  accountId: ID;
  number: E164;
  note: string;
  createdAt: ISODate;
}

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
POST/api/v1/blocklist๐Ÿ”’ Bearer tokenblocklist.create

Block a caller. Their calls are rejected and recorded with outcome `blocked`.

Request body
CreateBlockedCallerRequest
Response
BlockedCaller
Type definitions (5)
export interface CreateBlockedCallerRequest {
  number: string;
  note?: string;
}

export interface BlockedCaller {
  id: ID;
  accountId: ID;
  number: E164;
  note: string;
  createdAt: ISODate;
}

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/blocklist/{id}๐Ÿ”’ Bearer tokenblocklist.delete

Unblock a caller.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}

voicemail

The Replay service: messages, shared with the *98 phone menu.

GET/api/v1/voicemail/messages๐Ÿ”’ Bearer tokenvoicemail.list

List voicemail messages, newest first, optionally for one extension or status. Changes made by dialing *98 show here.

Query
VoicemailQuery
Response
VoicemailMessage[]
Type definitions (6)
export interface VoicemailQuery {
  extension_id?: ID;
  status?: VoicemailStatus;
}

export type ID = string;

export type VoicemailStatus = "new" | "saved";

export interface VoicemailMessage extends Recording {
  /** Whose box it was left in. */
  extension_id: ID;
  status: VoicemailStatus;
}

export interface Recording {
  id: ID;
  account_id: ID;
  call_id: ID;
  calling_number: string;
  called_number: string;
  started_at: ISODate;
  duration_ms: number;
  /** False while the call is still being recorded. */
  complete: boolean;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
Example request
GET /api/v1/voicemail/messages?status=new
PATCH/api/v1/voicemail/messages/{id}๐Ÿ”’ Bearer tokenvoicemail.update

Mark a message new or saved. The change also shows when dialing *98.

Request body
UpdateVoicemailRequest
Response
VoicemailMessage
Type definitions (6)
export interface UpdateVoicemailRequest {
  status: VoicemailStatus;
}

export type VoicemailStatus = "new" | "saved";

export interface VoicemailMessage extends Recording {
  /** Whose box it was left in. */
  extension_id: ID;
  status: VoicemailStatus;
}

export interface Recording {
  id: ID;
  account_id: ID;
  call_id: ID;
  calling_number: string;
  called_number: string;
  started_at: ISODate;
  duration_ms: number;
  /** False while the call is still being recorded. */
  complete: boolean;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
DELETE/api/v1/voicemail/messages/{id}๐Ÿ”’ Bearer tokenvoicemail.delete

Delete a message. The change also shows when dialing *98.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
GET/api/v1/voicemail/messages/{id}/audio๐Ÿ”’ Bearer tokenvoicemail.audio

Download a message as WAV (16-bit PCM mono, 8 kHz).

Response
WAV

recordings

The Replay service: recorded calls, served as WAV.

GET/api/v1/recordings๐Ÿ”’ Bearer tokenrecordings.list

List call recordings, newest first. Calls are recorded while the call recording add-on is on.

Response
Recording[]
Type definitions (3)
export interface Recording {
  id: ID;
  account_id: ID;
  call_id: ID;
  calling_number: string;
  called_number: string;
  started_at: ISODate;
  duration_ms: number;
  /** False while the call is still being recorded. */
  complete: boolean;
}

export type ID = string;

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/api/v1/recordings/{id}/audio๐Ÿ”’ Bearer tokenrecordings.audio

Download a recording as WAV (16-bit PCM mono, 8 kHz).

Response
WAV

tickets

The Tickets service: support tickets and their replies.

GET/api/v1/tickets๐Ÿ”’ Bearer tokentickets.list

List the account's support tickets with their status (open or closed), newest first.

Response
TicketSummary[]
Type definitions (4)
/** A ticket in a list. */
export interface TicketSummary {
  id: ID;
  title: string;
  status: TicketStatus;
  account_id: ID;
  created_at: ISODate;
}

export type ID = string;

export type TicketStatus = "open" | "closed";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;
GET/api/v1/tickets/{id}๐Ÿ”’ Bearer tokentickets.get

A ticket with its description and replies.

Response
Ticket
Type definitions (6)
export interface Ticket extends TicketSummary {
  /** The user who opened it. */
  user_id: ID;
  description: string;
  closed_at: ISODate | null;
  replies: TicketReply[];
}

/** A ticket in a list. */
export interface TicketSummary {
  id: ID;
  title: string;
  status: TicketStatus;
  account_id: ID;
  created_at: ISODate;
}

export type ID = string;

export type TicketStatus = "open" | "closed";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export interface TicketReply {
  /** Who wrote it: a user of the account, or provider staff. */
  user_id: string;
  text: string;
}
POST/api/v1/tickets๐Ÿ”’ Bearer tokentickets.create

Open a support ticket with a title and a description.

Request body
CreateTicketRequest
Response
Ticket
Type definitions (7)
export interface CreateTicketRequest {
  title: string;
  description: string;
}

export interface Ticket extends TicketSummary {
  /** The user who opened it. */
  user_id: ID;
  description: string;
  closed_at: ISODate | null;
  replies: TicketReply[];
}

/** A ticket in a list. */
export interface TicketSummary {
  id: ID;
  title: string;
  status: TicketStatus;
  account_id: ID;
  created_at: ISODate;
}

export type ID = string;

export type TicketStatus = "open" | "closed";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export interface TicketReply {
  /** Who wrote it: a user of the account, or provider staff. */
  user_id: string;
  text: string;
}
Example request
POST /api/v1/tickets
Content-Type: application/json

{
  "title": "No audio on calls to the UK",
  "description": "Calls connect but neither side hears anything."
}
POST/api/v1/tickets/{id}/replies๐Ÿ”’ Bearer tokentickets.reply

Add a reply to a ticket.

Request body
CreateTicketReplyRequest
Response
TicketReply
Type definitions (2)
export interface CreateTicketReplyRequest {
  text: string;
}

export interface TicketReply {
  /** Who wrote it: a user of the account, or provider staff. */
  user_id: string;
  text: string;
}
POST/api/v1/tickets/{id}/close๐Ÿ”’ Bearer tokentickets.close

Close a support ticket.

Response
Ticket
Type definitions (6)
export interface Ticket extends TicketSummary {
  /** The user who opened it. */
  user_id: ID;
  description: string;
  closed_at: ISODate | null;
  replies: TicketReply[];
}

/** A ticket in a list. */
export interface TicketSummary {
  id: ID;
  title: string;
  status: TicketStatus;
  account_id: ID;
  created_at: ISODate;
}

export type ID = string;

export type TicketStatus = "open" | "closed";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export interface TicketReply {
  /** Who wrote it: a user of the account, or provider staff. */
  user_id: string;
  text: string;
}

calls

The Call Manager: live calls, call control, and click-to-call.

GET/api/v1/calls/live๐Ÿ”’ Bearer tokencalls.live

Calls currently ringing, active, or on hold.

Response
LiveCall[]
Type definitions (13)
export interface LiveCall {
  id: ID;
  accountId: ID;
  direction: CallDirection;
  from: string;
  to: string;
  /** The account's number the call came in on or presents as caller ID. */
  did: E164 | null;
  /** Extension placing the call (outgoing and internal). */
  fromExtensionId: ID | null;
  /** Extensions ringing right now. */
  ringingExtensionIds: ID[];
  /** External numbers ringing with them. */
  ringingExternalNumbers: E164[];
  /** Extension talking on the call. */
  answeredExtensionId: ID | null;
  state: LiveCallState;
  codec: Codec | null;
  /** Seconds since the call started. */
  elapsedSeconds: number;
  /** Path taken through routing so far. */
  path: string[];
  /** For a call to *98: what the voicemail system is saying now. */
  voicemailPrompt: string | null;
  /** For an attended transfer: the held call this one consults about. */
  consultForCallId: ID | null;
}

export type ID = string;

export type CallDirection = "incoming" | "outgoing" | "internal";

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";

export type LiveCallState = "ringing" | "active" | "on-hold";
POST/api/v1/calls/{id}/actions๐Ÿ”’ Bearer tokencalls.action

Control a live call: answer, reject, hold, resume, hang up, forward, transfer (blind or attended), complete an attended transfer, or send a DTMF digit.

Request body
CallAction
Response
CallActionResponse
Type definitions (16)
export type CallAction =
  | { action: "answer"; extensionId: ID }
  | { action: "answer_external"; number: E164 }
  | { action: "reject"; extensionId?: ID }
  | { action: "hold" }
  | { action: "resume" }
  | { action: "hangup" }
  | { action: "forward"; to: string }
  | { action: "transfer"; to: string; type: "blind" | "attended" }
  | { action: "transfer_complete" }
  | { action: "dtmf"; digit: DtmfKey };

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export type DtmfKey = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";

export interface CallActionResponse {
  /** The call after the action, or null if it ended. */
  call: LiveCall | null;
  /** Attended transfer: the new call to the transfer target. */
  consultCall: LiveCall | null;
}

export interface LiveCall {
  id: ID;
  accountId: ID;
  direction: CallDirection;
  from: string;
  to: string;
  /** The account's number the call came in on or presents as caller ID. */
  did: E164 | null;
  /** Extension placing the call (outgoing and internal). */
  fromExtensionId: ID | null;
  /** Extensions ringing right now. */
  ringingExtensionIds: ID[];
  /** External numbers ringing with them. */
  ringingExternalNumbers: E164[];
  /** Extension talking on the call. */
  answeredExtensionId: ID | null;
  state: LiveCallState;
  codec: Codec | null;
  /** Seconds since the call started. */
  elapsedSeconds: number;
  /** Path taken through routing so far. */
  path: string[];
  /** For a call to *98: what the voicemail system is saying now. */
  voicemailPrompt: string | null;
  /** For an attended transfer: the held call this one consults about. */
  consultForCallId: ID | null;
}

export type CallDirection = "incoming" | "outgoing" | "internal";

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";

export type LiveCallState = "ringing" | "active" | "on-hold";
POST/api/v1/calls/dial๐Ÿ”’ Bearer tokencalls.dial

Place a call from an extension. Accepts US numbers as 10 digits or 1 + 10 digits, any number as E.164 (+) or with 011, another extension's number, or *98 for voicemail. Fails with 402 if the account is past due, and with 403 for destinations outside the US without international calling.

Request body
DialRequest
Response
LiveCall
Type definitions (14)
export interface DialRequest {
  /** Extension placing the call. */
  extensionId: ID;
  /** 10 digits, 1 + 10 digits, E.164, 011 + number, an extension, or *98. */
  to: string;
}

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

export type ID = string;

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type Presence = "online" | "offline" | "busy";

export interface LiveCall {
  id: ID;
  accountId: ID;
  direction: CallDirection;
  from: string;
  to: string;
  /** The account's number the call came in on or presents as caller ID. */
  did: E164 | null;
  /** Extension placing the call (outgoing and internal). */
  fromExtensionId: ID | null;
  /** Extensions ringing right now. */
  ringingExtensionIds: ID[];
  /** External numbers ringing with them. */
  ringingExternalNumbers: E164[];
  /** Extension talking on the call. */
  answeredExtensionId: ID | null;
  state: LiveCallState;
  codec: Codec | null;
  /** Seconds since the call started. */
  elapsedSeconds: number;
  /** Path taken through routing so far. */
  path: string[];
  /** For a call to *98: what the voicemail system is saying now. */
  voicemailPrompt: string | null;
  /** For an attended transfer: the held call this one consults about. */
  consultForCallId: ID | null;
}

export type CallDirection = "incoming" | "outgoing" | "internal";

export type LiveCallState = "ringing" | "active" | "on-hold";

history

The History service: call detail records with their charges, search, and period stats.

GET/api/v1/calls/history๐Ÿ”’ Bearer tokencdr.list

Search call history by phone number (full or partial), time range, direction, extension, and outcome. Newest first and paged with a cursor. Includes just-ended calls.

Query
CallHistoryQuery
Response
CursorPage<CallRecord>
Type definitions (18)
export interface CallHistoryQuery {
  /** Full or partial phone number, matched against calling and called. */
  phone?: string;
  /** Start time at or after this time. */
  from?: ISODate;
  /** Start time before this time. */
  to?: ISODate;
  direction?: CallDirection;
  /** Extension number. */
  extension?: string;
  outcome?: CallOutcome;
  cursor?: string;
  limit?: number;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

export type CallDirection = "incoming" | "outgoing" | "internal";

export interface Extension {
  id: ID;
  accountId: ID;
  /** Unique in the account, e.g. "101". */
  number: string;
  name: string;
  sipUsername: string;
  /** Null until the extension is active, and for non-admins. */
  sipPassword: string | null;
  status: ExtensionStatus;
  /** Billing purchase state, or null when included capacity was used. */
  purchaseState: string | null;
  purchaseId: string | null;
  /** Not reported yet: null. */
  voicemailBox: string | null;
  forwarding: Forwarding;
  /** External numbers that ring together with this extension. */
  simultaneousRing: E164[];
  userId: ID | null;
  /** Not reported yet: null. */
  devices: RegisteredDevice[] | null;
  /** Not reported yet: "unknown". */
  presence: Presence | "unknown";
  /** Beyond the plan's included extensions, so it has a recurring price. */
  billable: boolean;
  removing: boolean;
  worksUntil: ISODate | null;
  createdAt: ISODate;
}

export type ID = string;

/** `purchase_pending`: an extra extension waiting for payment; `pending`: its SIP login is being set up. */
export type ExtensionStatus = "purchase_pending" | "pending" | "active" | "removing" | "removed";

export interface Forwarding {
  always: ForwardTarget | null;
  busy: ForwardTarget | null;
  noAnswer: ForwardTarget | null;
  noAnswerSeconds: number;
}

export type ForwardTarget =
  | { type: "extension"; extensionId: ID }
  | { type: "external"; number: E164 };

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;

export interface RegisteredDevice {
  id: ID;
  userAgent: string;
  transport: "udp" | "tls";
  codec: Codec;
  /** TLS phones use SRTP with SDES. */
  srtp: boolean;
  contact: string;
  registeredAt: ISODate;
  expiresAt: ISODate;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

export type Presence = "online" | "offline" | "busy";

export type CallOutcome = "answered" | "busy" | "no_answer" | "failed" | "blocked";

export interface CursorPage<T> {
  items: T[];
  /** Pass as `cursor` to get the next page; null on the last page. */
  next_cursor: string | null;
  limit: number;
}

export interface CallRecord {
  id: ID;
  account_id: ID;
  direction: CallDirection;
  calling_number: string;
  called_number: string;
  did: E164 | null;
  /** Extension numbers on the call, e.g. ["101", "103"]. */
  extensions: string[];
  /** Path through routing, e.g. ["IVR: Main", "Pressed 2", "Ext 103"]. */
  path: string[];
  status: CallStatus;
  /** Null while active. */
  outcome: CallOutcome | null;
  codec: string | null;
  /** PSTN vendor that carried an outgoing call. */
  vendor: VendorName | null;
  start_time: ISODate;
  answer_time: ISODate | null;
  end_time: ISODate | null;
  recording_id: ID | null;
  /** Answered time after 30/6 rounding; null while active. */
  billable_seconds: number | null;
  /** What we bill, once Billing has rated it. */
  charge_cents: Cents | null;
  /** What the PSTN vendor charges us; null when unknown. */
  cost_cents: Cents | null;
  /** Billable seconds covered by the plan's included US minutes. */
  included_seconds: number | null;
  /** The rate used, e.g. "International (+44)". */
  rate_description: string | null;
}

/** `active` while it runs; `interrupted` if its media server stopped. */
export type CallStatus = "active" | "ended" | "interrupted";

export type VendorName = "us" | "international";

/** Amount in US cents. Per-call amounts and per-minute rates may be fractional. */
export type Cents = number;
Example request
GET /api/v1/calls/history?limit=1
GET/api/v1/calls/stats๐Ÿ”’ Bearer tokencdr.stats

Call totals for the current billing period.

Response
CallStats
Type definitions (2)
export interface CallStats {
  calls_this_period: number | null;
  /** Answered รท finished calls, 0โ€“1. */
  answered_rate: number | null;
  avg_duration_seconds: number | null;
  active_calls: number;
  period_start: ISODate | null;
  period_end: ISODate | null;
}

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

settings

The Call Manager: codec preferences and hold music (caller ID is the Numbers service's).

GET/api/v1/settings/phone๐Ÿ”’ Bearer tokensettings.phone

Codec preferences and hold music. `callerIdNumberId` is always null: the caller ID is Numbers' (`numbers.callerId`).

Response
PhoneSettings
Type definitions (4)
export interface PhoneSettings {
  /** Ordered by preference; first is offered first in SDP. */
  codecPriority: Codec[];
  enabledCodecs: Codec[];
  holdMusic: string;
  /** The account's number (E.164) shown as caller ID on outgoing calls. */
  callerIdNumberId: E164 | null;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;
PATCH/api/v1/settings/phone๐Ÿ”’ Bearer tokensettings.updatePhone

Partially update codec preferences and hold music. Admins only. Every enabled codec must be in the priority list.

Request body
UpdatePhoneSettingsRequest
Response
PhoneSettings
Type definitions (5)
export type UpdatePhoneSettingsRequest = Partial<PhoneSettings>;

export interface PhoneSettings {
  /** Ordered by preference; first is offered first in SDP. */
  codecPriority: Codec[];
  enabledCodecs: Codec[];
  holdMusic: string;
  /** The account's number (E.164) shown as caller ID on outgoing calls. */
  callerIdNumberId: E164 | null;
}

export type Codec = "PCMU" | "PCMA" | "G729" | "G723" | "OPUS";

export type ID = string;

/** E.164 phone number, e.g. "+16315550142" */
export type E164 = string;
Example request
PATCH /api/v1/settings/phone
Content-Type: application/json

{
  "codecPriority": [
    "OPUS",
    "PCMU",
    "PCMA",
    "G729",
    "G723"
  ]
}

provider

For provider staff: open tickets across all accounts.

GET/api/v1/provider/tickets๐Ÿ”’ Provider tokenprovider.tickets

Open support tickets across all accounts, newest first. Provider token required.

Response
ProviderTicket[]
Type definitions (5)
/** The provider's queue lists every account's open tickets. */
export type ProviderTicket = TicketSummary;

/** A ticket in a list. */
export interface TicketSummary {
  id: ID;
  title: string;
  status: TicketStatus;
  account_id: ID;
  created_at: ISODate;
}

export type ID = string;

export type TicketStatus = "open" | "closed";

/** ISO-8601 timestamp, e.g. "2026-10-28T14:03:00Z" */
export type ISODate = string;

softphone

The Call Manager: register the browser as a web phone and place and answer calls over WebRTC.

POST/api/v1/softphone/devices๐Ÿ”’ Bearer tokensoftphone.register

Register this browser as a web phone for an extension: calls to the extension ring it too, and it can call as the extension. Send `deviceId` to keep the same one. Returns the STUN/TURN servers for WebRTC.

Request body
RegisterWebPhoneRequest
Response
WebPhone
Type definitions (4)
export interface RegisterWebPhoneRequest {
  extensionId: ID;
  /** Keep the same web phone as an earlier registration. */
  deviceId?: ID | null;
}

export type ID = string;

export interface WebPhone {
  deviceId: ID;
  extensionId: ID;
  /** Poll its calls more often than this to stay registered. */
  expiresInSeconds: number;
  iceServers: IceServer[];
}

export interface IceServer {
  urls: string[];
  username?: string | null;
  credential?: string | null;
}
DELETE/api/v1/softphone/devices/{deviceId}๐Ÿ”’ Bearer tokensoftphone.unregister

Unregister a web phone.

Response
Ok
Type definitions (1)
export interface Ok {
  ok: true;
}
GET/api/v1/softphone/devices/{deviceId}/calls๐Ÿ”’ Bearer tokensoftphone.calls

The web phone's calls: ringing (with the PBX's WebRTC offer), answered, and recently ended. Polling keeps it registered; 404 once it expired.

Response
WebCall[]
Type definitions (2)
export interface WebCall {
  /** The browser's leg (`web:โ€ฆ`). */
  id: string;
  /** The live call, for hold, transfer and keys. */
  callId: ID | null;
  direction: "incoming" | "outgoing";
  state: "ringing" | "answered" | "ended";
  /** The caller's number, or the number dialed. */
  peer: string;
  /** Incoming: the PBX's WebRTC offer. */
  offer: string | null;
  /** Outgoing: the PBX's WebRTC answer once answered. */
  answer: string | null;
  /** 180 while the far end rings; 486, 480, 403โ€ฆ when it failed. */
  status: number | null;
}

export type ID = string;
POST/api/v1/softphone/devices/{deviceId}/calls๐Ÿ”’ Bearer tokensoftphone.dial

Call from the web phone with the browser's complete WebRTC offer (ICE gathered). Poll its calls for the answer. Authorized like the extension's desk phone.

Request body
WebDialRequest
Response
WebCall
Type definitions (4)
export interface WebDialRequest extends WebSdpRequest {
  to: string;
}

export interface WebSdpRequest {
  sdp: string;
}

export interface WebCall {
  /** The browser's leg (`web:โ€ฆ`). */
  id: string;
  /** The live call, for hold, transfer and keys. */
  callId: ID | null;
  direction: "incoming" | "outgoing";
  state: "ringing" | "answered" | "ended";
  /** The caller's number, or the number dialed. */
  peer: string;
  /** Incoming: the PBX's WebRTC offer. */
  offer: string | null;
  /** Outgoing: the PBX's WebRTC answer once answered. */
  answer: string | null;
  /** 180 while the far end rings; 486, 480, 403โ€ฆ when it failed. */
  status: number | null;
}

export type ID = string;
POST/api/v1/softphone/calls/{key}/answer๐Ÿ”’ Bearer tokensoftphone.answer

Answer a call ringing in the browser with its WebRTC answer.

Request body
WebSdpRequest
Response
WebCall
Type definitions (3)
export interface WebSdpRequest {
  sdp: string;
}

export interface WebCall {
  /** The browser's leg (`web:โ€ฆ`). */
  id: string;
  /** The live call, for hold, transfer and keys. */
  callId: ID | null;
  direction: "incoming" | "outgoing";
  state: "ringing" | "answered" | "ended";
  /** The caller's number, or the number dialed. */
  peer: string;
  /** Incoming: the PBX's WebRTC offer. */
  offer: string | null;
  /** Outgoing: the PBX's WebRTC answer once answered. */
  answer: string | null;
  /** 180 while the far end rings; 486, 480, 403โ€ฆ when it failed. */
  status: number | null;
}

export type ID = string;
POST/api/v1/softphone/calls/{key}/reject๐Ÿ”’ Bearer tokensoftphone.reject

Decline a call ringing in the browser; other phones keep ringing.

Response
WebCall
Type definitions (2)
export interface WebCall {
  /** The browser's leg (`web:โ€ฆ`). */
  id: string;
  /** The live call, for hold, transfer and keys. */
  callId: ID | null;
  direction: "incoming" | "outgoing";
  state: "ringing" | "answered" | "ended";
  /** The caller's number, or the number dialed. */
  peer: string;
  /** Incoming: the PBX's WebRTC offer. */
  offer: string | null;
  /** Outgoing: the PBX's WebRTC answer once answered. */
  answer: string | null;
  /** 180 while the far end rings; 486, 480, 403โ€ฆ when it failed. */
  status: number | null;
}

export type ID = string;
POST/api/v1/softphone/calls/{key}/hangup๐Ÿ”’ Bearer tokensoftphone.hangup

Hang up the browser's call, or cancel one it is still placing.

Response
WebCall
Type definitions (2)
export interface WebCall {
  /** The browser's leg (`web:โ€ฆ`). */
  id: string;
  /** The live call, for hold, transfer and keys. */
  callId: ID | null;
  direction: "incoming" | "outgoing";
  state: "ringing" | "answered" | "ended";
  /** The caller's number, or the number dialed. */
  peer: string;
  /** Incoming: the PBX's WebRTC offer. */
  offer: string | null;
  /** Outgoing: the PBX's WebRTC answer once answered. */
  answer: string | null;
  /** 180 while the far end rings; 486, 480, 403โ€ฆ when it failed. */
  status: number | null;
}

export type ID = string;