Docs › SDKs
TypeScript SDK
@handset/sdk is generated from the OpenAPI spec —
one typed function per endpoint, ESM and CommonJS, zero dependencies beyond a
fetch-capable runtime (Node 18+, Bun, Deno, edge workers, browsers).
Install & configure
npm install @handset/sdk
Configure the shared client once at startup — every SDK function uses it. The base URL defaults to production; only the key is required.
import { client } from "@handset/sdk/client"; client.setConfig({ headers: { Authorization: `Bearer ${process.env.HANDSET_API_KEY}` }, });
Every call returns an envelope
SDK functions never throw on API errors. Each returns
{ data, error, response }:
import { sendMessage } from "@handset/sdk"; const res = await sendMessage({ body: { from: "num_01kzv…", to: "+14155550123", body: "On my way!" }, }); if (res.error) { console.error(res.error.error.code); // e.g. "recipient_opted_out" } else { console.log(res.data.id, res.data.status); // msg_…, "queued" }
Error codes worth branching on: recipient_opted_out (they sent
STOP — do not retry), campaign_not_approved (10DLC in review),
rate_limited, and tenant_not_found.
Idempotent sends
await sendMessage({ body: { from: "num_…", to: "+1415…", body: "Appointment confirmed." }, headers: { "Idempotency-Key": crypto.randomUUID() }, });
The core loop
import { createTenant, searchAvailableNumbers, purchaseNumber, sendMessage, listConversations, } from "@handset/sdk"; // 1. Your customer, as a tenant const tenant = await createTenant({ body: { name: "Bayview Dental", timezone: "America/Los_Angeles" }, }); // 2. A number in their area code, live on arrival const found = await searchAvailableNumbers({ query: { area_code: "415" } }); const number = await purchaseNumber({ body: { tenant_id: tenant.data!.id, phone_number: found.data!.data![0].phone_number! }, }); // 3. Send — threading, opt-outs, 10DLC all happen server-side await sendMessage({ body: { from: number.data!.id, to: "+14155550123", body: "Welcome aboard!" }, }); // 4. Read the thread back for your UI const threads = await listConversations({ query: { tenant_id: tenant.data!.id } });
Pagination
let after: string | undefined; do { const page = await listMessages({ query: { limit: 100, after } }); for (const m of page.data?.data ?? []) handle(m); after = page.data?.has_more ? page.data.next_cursor ?? undefined : undefined; } while (after);
Types
Every request and response shape is exported — the SDK is also your type library:
import type { Message, Conversation, Tenant, Voicemail } from "@handset/sdk"; function renderBubble(m: Message) { /* m.direction, m.status, m.body… */ }
A Python package (
handset) ships
the same way — ask us for early
access.