TypeScript SDK

The official TypeScript/JavaScript client — typed resources, cursor pagination helpers, conservative retries, and typed errors. Zero runtime dependencies.

npm install dextelai-typescript-sdk

Requires Node 18+ (or any runtime with a global fetch). The package has no runtime dependencies — nothing extra to audit or keep patched.

Quick start

index.ts
import { Dextel } from "dextelai-typescript-sdk";

// The key determines the tenant — you never pass an account id.
const dextel = new Dextel({ apiKey: process.env.DEXTEL_API_KEY! });

const { data: contacts } = await dextel.contacts.list({ limit: 10 });
console.log(contacts);

Keep your key out of source control

Create a key in the dashboard under API Keysand read it from the environment. A key committed to a repository is a key in everyone's clone, including forks.

Key scopes

There are two kinds of key and they are not interchangeable. A sub-account key reaches agents, contacts, calendars, appointments and calls. An agency key reaches dextel.agency.*. The tenant is derived from the key itself, so you never pass an agency_id or sub_account_id. Calling an agency method with a sub-account key throws PermissionError.

Resources

dextel.agents            // list, retrieve, create, update, del
dextel.contacts          // list, retrieve, create, update, del
dextel.calendars         // list, retrieve, create, update, del
dextel.appointments      // list, retrieve, create, update, del
dextel.knowledgeBases    // list, retrieve, create, del
dextel.customTools       // list, retrieve, create, del
dextel.phoneNumbers      // list, retrieve            (read-only)
dextel.numberPools       // list, retrieve            (read-only)
dextel.calls             // list, retrieve            (read-only)
dextel.workflows         // list, retrieve            (read-only)
dextel.agency            // retrieve, listSubAccounts, retrieveSubAccount

Read-only resources genuinely have no create, update or del method — calling one is a compile error rather than a 404 you discover in production.

Create, update, delete

const agent = await dextel.agents.create({
  name: "Front Desk",
});

const updated = await dextel.agents.update(agent.id, {
  is_active: true,
});

await dextel.agents.del(agent.id);

Pagination

The API uses cursor pagination, not page numbers. The SDK gives you three ways to consume it.

// 1. One page, with the cursor exposed.
const page = await dextel.contacts.list({ limit: 50 });
page.data;                    // Contact[]
page.pagination.has_more;     // boolean
page.pagination.next_cursor;  // pass back as starting_after

// 2. Stream every object — one page held in memory at a time.
for await (const contact of dextel.contacts.iterate()) {
  console.log(contact.email);
}

// 3. Collect everything (fine for small sets).
const all = await dextel.contacts.listAll();
Prefer iterate() over listAll() for large collections — it holds one page in memory rather than the entire set.

Errors

Every failure throws a typed error, so you branch with instanceof rather than comparing status numbers at each call site.

import {
  NotFoundError,
  RateLimitError,
} from "dextelai-typescript-sdk";

try {
  await dextel.agents.retrieve("ag_missing");
} catch (e) {
  if (e instanceof NotFoundError) return null;
  if (e instanceof RateLimitError) {
    await sleep((e.retryAfterSeconds ?? 1) * 1000);
  }
  throw e;
}

The classes are ValidationError (400, 422), AuthenticationError (401), PermissionError (403), NotFoundError (404), RateLimitError (429, carrying retryAfterSeconds), ServerError (5xx), and ConnectionError when no response arrived at all. All carry .status, .requestId and .rateLimit. Your API key never appears in an error message or stack trace.

Retries

Retries are deliberately conservative. GET and HEAD retry on 408, 429 and 5xx. Writes — POST, PATCH and DELETE — retry only on 429, because a 429 means the request never ran. Any other failed write is not retried: the API has no idempotency keys, so repeating a POST could create a second contact.

Backoff honours Retry-Afterwhen the server sends one, and otherwise uses exponential backoff with full jitter — so that many clients rate-limited at the same moment don't retry in lockstep and collide again.

Rate limits

The current window is available after any call via dextel.rateLimit, parsed from the response headers into limit, remaining and resetAt.

Configuration

const dextel = new Dextel({
  apiKey: process.env.DEXTEL_API_KEY!,           // required
  baseUrl: "https://api.dextelai.com/api/v1",    // default
  timeout: 30_000,                               // ms, default 30s
  maxRetries: 2,                                 // default 2
  headers: { "x-trace-id": "..." },              // merged into every request
});

Escape hatch

If an endpoint isn't wrapped yet, call it directly through the underlying client — you keep authentication, retries and error mapping.

const res = await dextel.client.get("/some/new/endpoint", { limit: 10 });
await dextel.client.post("/another", { field: "value" });