handset docs

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

shell
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.

setup.ts
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 }:

send.ts
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

idempotent.ts
await sendMessage({
  body: { from: "num_…", to: "+1415…", body: "Appointment confirmed." },
  headers: { "Idempotency-Key": crypto.randomUUID() },
});

The core loop

onboard.ts
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

paginate.ts
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:

types.ts
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.