The official TypeScript / JavaScript client for the VoiceTel REST API β provision numbers, place orders, validate e911, send messages, and manage your account, with end-to-end TypeScript types and zero external dependencies.
- Features
- Installation
- Quickstart
- Authentication
- Resource Reference
- Error Handling
- Cancellation and Timeouts
- Rate Limits
- Browser Support
- Development
- API Documentation
- Contributors
- Sponsors
- License
- TypeScript interfaces for every one of the 73 API operations β request bodies, response payloads, and entity types.
- Strict mode-friendly. Authored against
strict,noUncheckedIndexedAccess, andexactOptionalPropertyTypesstyle rules. - Autocomplete everywhere. Your IDE knows the shape of every field β no more guessing what's in
result.data.numbers.
- Built on the standard
fetchAPI. Nonode-fetch, noaxios, no third-party HTTP dependency. - Pure ESM with a CJS fallback β works with
importandrequire. - Runs unchanged in Node.js 18+, Deno, Bun, and modern browsers.
- Automatic retry with exponential backoff on 429 / 5xx β honors
Retry-Afterheaders, capped at 8s. - AbortController-aware. Every method accepts
{ signal }for cancellation. - Per-request timeout managed by the SDK; falls back to
30000ms. - Bearer auth managed for you; passwordβkey exchange is one
await client.login()call. - Structured
ApiErrorwith typedkindso you canswitch err.kind { case "rate_limit": ... }without parsing HTTP status codes.
- Numbers β list, get, add, remove, route, translate, CNAM, LIDB, fax, forward, SMS, messaging campaigns, port-out PIN, account moves.
- Account β profile, sub-accounts, CDRs, credits, payments, MRC, registration, password recovery.
- e911 β record provisioning, address validation, lookup, removal.
- Gateways β list, create, update, delete, view bound numbers.
- Messaging β SMS & MMS sending, message history, 10DLC brand and campaign registration, per-number messaging state.
- Lookups β CNAM and LRN dips.
- iNumbering β inventory search, coverage queries, number orders, port-in submissions, port-out availability checks.
- Support β ticket create / read / update / delete, threaded messages, replies.
- ACL β IP allowlist management with structured 409 conflict bodies.
- Authentication β switch between Digest, IP-only, or hybrid modes; rotate passwords.
- 40 tests at 96% statement coverage with
vitest. - Mocked fetch + every method and error path covered.
- Strict TypeScript with
tsc --noEmitenforced in CI.
- Zero codegen footprint β every byte hand-written.
- Built with
tsup; ships ESM + CJS +.d.tsdeclarations in one package. - Zero runtime dependencies.
npm install @voicetel/sdk
# or
pnpm add @voicetel/sdk
# or
yarn add @voicetel/sdkRequires Node.js 18 or later (for the global fetch API). Browsers and Deno/Bun work without polyfills.
import { VoiceTelClient } from "@voicetel/sdk";
const client = new VoiceTelClient();
// Exchange username + password for an API key (one-time per session)
await client.login(1000000001, "hunter2");
// Typed responses β your editor knows what `me` is.
const me = await client.account.get();
console.log(`Balance: $${me.cash?.toFixed(2)} | Caller ID: ${me.callerId}`);
// List your numbers
const { numbers } = await client.numbers.list();
for (const n of numbers) {
console.log(`${n.number} route=${n.route} cnam=${n.cnam} sms=${n.smsEnabled}`);
}Or, if you already have an API key:
const client = new VoiceTelClient({ apiKey: "32hex..." });
const { coverage } = await client.iNumbering.coverage({ state: "NJ" });
for (const bucket of coverage) {
console.log(`${bucket.npa}-${bucket.nxx}: ${bucket.count} TNs available`);
}Every endpoint requires Authorization: Bearer <apikey> except POST /v2.2/account/api-key, which exchanges username + password for a fresh key. client.login() handles the exchange and installs the returned key on the transport.
Re-fetch the API key after any password change β the old one is invalidated.
Don't have credentials yet? Get them at voicetel.com/docs/api/v2.2/credentials.
const client = new VoiceTelClient();
const key = await client.login(1000000001, "hunter2");
// `key` is the new 32-hex bearer; the client already has it installed.| Resource | Field on Client | Example |
|---|---|---|
| Account | client.account |
client.account.cdr({ start, end }) |
| ACL | client.acl |
client.acl.add({ acl: [{ cidr: "..." }] }) |
| Authentication | client.authentication |
client.authentication.update({ authType: 1 }) |
| e911 | client.e911 |
client.e911.validate({ address1, ... }) |
| Gateways | client.gateways |
client.gateways.list() |
| iNumbering | client.iNumbering |
client.iNumbering.searchInventory({ npa: 201 }) |
| Lookups | client.lookups |
client.lookups.lrn("2015551234", "2012548000") |
| Messaging | client.messaging |
client.messaging.send({ fromNumber, toNumber, text }) |
| Numbers | client.numbers |
client.numbers.assignCampaign("2015551234", { campaignId }) |
| Support | client.support |
client.support.create({ subject, message }) |
All request and response shapes are exported from the package β destructure what you need:
import {
VoiceTelClient,
type MessageSendRequest,
type PortSubmitRequest,
type NumberCampaignAssignRequest,
} from "@voicetel/sdk";
const send: MessageSendRequest = {
fromNumber: "2012548000",
toNumber: "2015551234",
text: "Your code is 482917",
};
const sent = await client.messaging.send(send);
console.log(`Sent: ${sent.id} (${sent.parts} segment(s))`);All HTTP errors throw ApiError with a typed kind:
kind |
HTTP status |
|---|---|
"bad_request" |
400 |
"authentication" |
401 |
"permission_denied" |
403 |
"not_found" |
404 |
"conflict" |
409 |
"rate_limit" |
429 |
"server" |
5xx |
"unknown" |
other / transport |
Or use the type guards:
import { isNotFound, isRateLimit } from "@voicetel/sdk";
try {
const n = await client.numbers.get("9999999999");
} catch (err) {
if (isNotFound(err)) {
console.log("That number isn't on your account.");
} else if (isRateLimit(err)) {
console.log("Slow down β backoff and retry.");
} else {
throw err;
}
}For 409 conflicts on ACL or auth, the structured failure payload is on err.body:
import { ApiError, isConflict, type AclConflictData } from "@voicetel/sdk";
try {
await client.acl.add({ acl: [{ cidr: "abc" }] });
} catch (err) {
if (isConflict(err)) {
const body = err.body as AclConflictData;
console.log("Failed CIDRs:", body.failed);
}
}Every method accepts { signal } for AbortController-based cancellation:
const ac = new AbortController();
setTimeout(() => ac.abort(), 1000);
const me = await client.account.get({ signal: ac.signal });Or set a per-client default timeout:
const client = new VoiceTelClient({
apiKey: "...",
timeoutMs: 5000,
});These endpoints are limited to 6 requests per hour per IP:
account/infoaccount/mrc(client.account.recurringCharges())account/cdr(client.account.cdr())account/api-key(client.login())
The SDK automatically retries 429 responses with Retry-After honored, up to maxRetries (default 2). To bump it:
const client = new VoiceTelClient({
apiKey,
maxRetries: 4,
timeoutMs: 60_000,
});The SDK runs unchanged in modern browsers β it depends only on fetch, URL, URLSearchParams, and AbortController. No process, no Node-specific imports.
CORS is your responsibility: the VoiceTel API doesn't currently set permissive CORS headers, so direct browser usage typically requires a backend proxy or your own CORS-friendly wrapper.
git clone https://github.com/voicetel/typescript-sdk
cd typescript-sdk
npm install
# Type check
npm run typecheck
# Lint
npm run lint
# Tests
npm test
# Tests with coverage
npm run test:cov
# Build
npm run build- Reference docs: voicetel.com/docs/api/v2.2/
- Interactive playground: voicetel.com/docs/api/v2.2/playground/ β try the API in your browser without writing any code
- API credentials: voicetel.com/docs/api/v2.2/credentials/
- Michael Mavroudis β Lead Developer
Contributions welcome. Open an issue describing the change, or send a pull request against main.
| Sponsor | Contribution |
|---|---|
| VoiceTel Communications | Primary development and production hosting |
This project is licensed under the MIT License β see the LICENSE file for details.