# TypeScript SDK

> @usebluebot/sdk for Node, Bun, Deno and edge runtimes: the client, every method, pagination, idempotency and retries, typed errors, webhook verification and the types it exports.

`@usebluebot/sdk` is BlueBot’s official SDK for TypeScript and JavaScript. It has one method for each API operation, fully typed, and it handles idempotency keys, retries, pagination and webhook signatures for you. It uses the platform’s own `fetch` and has no dependencies.

## Install

```bash title="npm"
npm install @usebluebot/sdk
```

```bash title="pnpm"
pnpm add @usebluebot/sdk
```

```bash title="yarn"
yarn add @usebluebot/sdk
```

```bash title="bun"
bun add @usebluebot/sdk
```

In Deno, import it straight from npm: `import BlueBot from 'npm:@usebluebot/sdk';`.

It runs on Node 18 and newer, Bun, Deno, Cloudflare Workers, Vercel and Netlify functions, and any runtime with `fetch`. It ships ES modules and CommonJS, with type declarations for both.

Check the name before installing. The SDK is `@usebluebot/sdk`, under the `@usebluebot` scope; the unscoped `bluebot` package on npm is somebody else’s.

## Make a client

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });
```

`BlueBot` is the default export and also a named one (`import { BlueBot } from '@usebluebot/sdk'`; `const { BlueBot } = require('@usebluebot/sdk')` in CommonJS). Make one client and share it; it holds no connection of its own.

| Option                    | Type                     | Default                                                              | What it does                                                                                                                                                           |
| ------------------------- | ------------------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`                  | `string`                 | `process.env.BLUEBOT_API_KEY`                                        | Your `bb_live_…` key. The constructor throws a `BlueBotError` if there’s none.                                                                                         |
| `baseUrl`                 | `string`                 | `process.env.BLUEBOT_BASE_URL`, else `https://usebluebot.com/api/v1` | Change it only to reach a BlueBot you run locally.                                                                                                                     |
| `timeout`                 | `number`                 | `60000`                                                              | Milliseconds to wait for each try.                                                                                                                                     |
| `maxRetries`              | `number`                 | `2`                                                                  | Retries after the first try, for what is safe to retry.                                                                                                                |
| `defaultHeaders`          | `Record<string, string>` | none                                                                 | Sent on every request.                                                                                                                                                 |
| `fetch`                   | `typeof fetch`           | the global `fetch`                                                   | Your own `fetch`, for a proxy or for tests.                                                                                                                            |
| `dangerouslyAllowBrowser` | `boolean`                | `false`                                                              | The client refuses to start in a browser unless this is `true`, because a key in a web page can be read by anyone who opens it. Call BlueBot from your server instead. |

`client.withOptions({ timeout: 10_000, maxRetries: 0 })` returns a copy with some options changed, sharing the rest.

## Send a text

```ts
const message = await client.messages.send({
  to: '+15555550123',
  text: 'Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.',
  metadata: { crm_id: '48213' },
});

console.log(message.id, message.state); // msg_1Hq2LxW9cVbN3mKpRtYs0a queued
if (message.hold) console.log(`Waiting: ${message.hold.description}`);
```

The answer is the message, usually `queued`. A text that can’t go yet (quiet hours, your Mac offline, a daily limit) is accepted with a `hold` saying why and until when; it isn’t an error, and it goes on its own when the hold ends. To learn when it was sent, delivered or read, use [webhooks](https://usebluebot.com/docs/webhooks) rather than asking again and again.

## Names in and out

The API speaks snake_case JSON. The SDK speaks camelCase both ways: you pass `sendAt` and `chatId`, and read `message.sendAt`, `message.chatId`, `page.hasMore` and `page.nextCursor`. Keys inside `metadata` and `customFields` are yours, so they are sent and returned exactly as written. A field that’s one word (`to`, `text`, `state`, `from`) is the same either way.

| In the API                                       | In the SDK                                      |
| ------------------------------------------------ | ----------------------------------------------- |
| `send_at`, `expires_at`, `chat_id`               | `sendAt`, `expiresAt`, `chatId`                 |
| `hold.reason`, `failure.reason`, `cancel_reason` | `hold.reason`, `failure.reason`, `cancelReason` |
| `has_more`, `next_cursor`                        | `hasMore`, `nextCursor`                         |
| `metadata.crm_id`                                | `metadata.crm_id`                               |

Times you pass may be a `Date` or an ISO 8601 string with a zone. Times you get back are ISO 8601 strings in UTC, such as `"2026-11-13T19:41:00.000Z"`.

## Messages

```ts
// Schedule one. It stays a draft you can change until it's due.
const reminder = await client.messages.send({
  to: '+15555550123',
  text: 'Reminder: your cleaning is tomorrow at 10 AM. Reply C to confirm.',
  sendAt: new Date('2026-11-20T15:00:00Z'),
  metadata: { appointment_id: 'apt_9921' },
});

// Move it, or change the words, before it goes.
await client.messages.update(reminder.id, { sendAt: '2026-11-21T15:00:00Z' });

// Or cancel it. A text the Mac already took can't be called back: that's a ConflictError.
await client.messages.cancel(reminder.id);

// Text an existing group chat instead of one person.
await client.messages.send({ chatId: 'chat_1Hq2LxW9cVbN3mKpRtYs0a', text: 'Running 10 minutes late.' });

// Insist on iMessage (no SMS fallback), and give up if it hasn't gone in an hour.
await client.messages.send({
  to: 'maria@example.com',
  text: 'Here is the link you asked for.',
  service: 'imessage',
  expiresAt: new Date(Date.now() + 60 * 60 * 1000),
});

// Read one.
const sent = await client.messages.get('msg_1Hq2LxW9cVbN3mKpRtYs0a');

// Settle a text the Mac couldn't confirm: you checked and it went, or send it again.
await client.messages.resolve('msg_1Hq2LxW9cVbN3mKpRtYs0a', { action: 'mark_sent' });
```

`service` is `'auto'` by default: the service of the newest chat with the person, else iMessage, with one SMS retry for an iMessage that never went. [Services and SMS fallback](https://usebluebot.com/docs/concepts/services) explains the choice.

## Files

Upload a file first, then send its id. A file nobody sends is deleted after a day.

```ts
import { openAsBlob } from 'node:fs'; // Node 20 and newer

const photo = await client.files.upload(await openAsBlob('house.jpg'), { filename: 'house.jpg' });
await client.messages.send({ to: '+15555550123', text: 'Here is the front of the house.', files: [photo.id] });
```

`upload` takes a `Blob` or `File`, or a `Uint8Array` or `ArrayBuffer` with a `filename`. On Node 18, read the file into a `Blob` yourself:

```ts
import { readFile } from 'node:fs/promises';

const bytes = await readFile('menu.pdf');
const menu = await client.files.upload(new Blob([bytes], { type: 'application/pdf' }), { filename: 'menu.pdf' });
```

A file another app hands you as a URL comes over with `fetch`:

```ts
const response = await fetch('https://example.com/flyer.png');
const flyer = await client.files.upload(await response.blob(), { filename: 'flyer.png' });
```

Files that arrive on a text are on `message.files`. Each has a `url` that works for an hour while its `status` is `ready`; `client.files.get(id)` gives a fresh one. A voice memo may carry a `transcript`. [Files and voice memos](https://usebluebot.com/docs/sending/files) has the sizes and kinds.

## Lists and pagination

Every list call answers newest first. Await it for one page, or loop over it to walk every item:

```ts
// One page.
const page = await client.messages.list({ direction: 'in', limit: 50 });
console.log(page.data.length, page.hasMore, page.nextCursor);

// Every item, page after page.
for await (const message of client.messages.list({ state: 'failed', since: '2026-11-01T00:00:00Z' })) {
  console.log(message.to, message.failure?.reason);
}

// By hand.
let current = await client.chats.list({ isGroup: true });
while (current) {
  for (const chat of current.data) console.log(chat.name);
  current = current.hasNextPage() ? await current.getNextPage() : null;
}
```

A page has `data`, `hasMore`, `nextCursor`, `hasNextPage()` and `getNextPage()`. `limit` is 1 to 100, 25 by default. Looping stops when you `break`, and fetches nothing more than it needs.

## Lookup, the Mac and your account

```ts
// What did the newest chat with this number use? Reads your own history through your Mac.
const lookup = await client.lookup('+15555550123');
console.log(lookup.service, lookup.basis); // 'imessage' 'history'

// Is the Mac there, and does it need you?
const device = await client.device.get();
if (device.status !== 'online') console.log(`Offline since ${device.offlineSince}`);
for (const [name, check] of Object.entries(device.checks)) {
  if (check.state === 'needs_you') console.log(`${name}: ${check.detail}`);
}
console.log(`${device.today.newPeople} of ${device.today.newPeopleLimit} new people today`);

// BlueBot's own status, and who this key is.
const status = await client.status();
const me = await client.me();
console.log(status.sending, me.account.email, me.key?.scopes);
```

A lookup of a number you’ve never texted answers `service: 'unknown'`, since Apple offers no way to ask. [Lookups](https://usebluebot.com/docs/sending/lookups) explains what the answer rests on.

## Settings and stops

```ts
const settings = await client.settings.update({
  timeZone: 'America/Chicago',
  paceSeconds: 30,
  quietHours: { enabled: true, start: '20:00', end: '08:00' },
});
console.log(settings.warnings);

// Pause all sending for two hours, then resume.
await client.settings.update({ pausedUntil: new Date(Date.now() + 2 * 60 * 60 * 1000) });
await client.settings.update({ pausedUntil: null });

// The stop list.
await client.stops.add({ person: '+15555550144' });
for await (const stop of client.stops.list()) console.log(stop.handle, stop.source);
await client.stops.remove('+15555550144'); // the SDK encodes the + for the path
```

## Events

The last 30 days of events, the same ones webhooks carry, newest first:

```ts
for await (const event of client.events.list({ type: ['message.received', 'message.failed'], since: '2026-11-12T00:00:00Z' })) {
  console.log(event.type, event.timestamp);
}
const event = await client.events.get('evt_1Hq2LxW9cVbN3mKpRtYs0a');
```

`type` takes a string of comma-separated types or an array.

## Webhooks

```ts
const webhook = await client.webhooks.create({
  url: 'https://example.com/bluebot/webhooks',
  events: ['message.received', 'message.failed'],
  description: 'Replies into the CRM',
});
// The secret is shown now and when you rotate it, never again. Store it.
console.log(webhook.secret);

await client.webhooks.test(webhook.id, { type: 'message.received' });
await client.webhooks.update(webhook.id, { paused: true });

// A new secret; the old one keeps signing beside it for 24 hours.
const { secret } = await client.webhooks.rotateSecret(webhook.id);

// Send again everything that gave up.
for await (const delivery of client.webhooks.deliveries.list(webhook.id, { state: 'gave_up' })) {
  await client.webhooks.deliveries.replay(webhook.id, delivery.id);
}

await client.webhooks.delete(webhook.id);
```

### Verify what BlueBot sends

`client.webhooks.verify(payload, headers, secret)` checks a delivery’s Standard Webhooks signature and timestamp and answers the event, typed by its `type`. Pass the raw body exactly as it arrived: parsing it and serializing it again changes the bytes and breaks the signature.

| Argument            | Type                               | What it is                                                                                                      |
| ------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `payload`           | `string`, `Uint8Array` or `Buffer` | The raw body.                                                                                                   |
| `headers`           | `Headers` or a plain object        | The request’s headers; `webhook-id`, `webhook-timestamp` and `webhook-signature` are read from it, in any case. |
| `secret`            | `string`                           | The webhook’s `whsec_…` secret. Default: `process.env.BLUEBOT_WEBHOOK_SECRET`.                                  |
| `options.tolerance` | `number`                           | Seconds a timestamp may be from now, either way. Default `300`.                                                 |

It throws `WebhookVerificationError` when a header is missing, the timestamp is outside the tolerance, no signature matches, or the body isn’t JSON. While a secret is being rotated, a delivery carries two signatures and either one passes.

```ts title="app/api/bluebot/route.ts (Next.js)"
import BlueBot, { WebhookVerificationError } from '@usebluebot/sdk';

const client = new BlueBot();

export async function POST(request: Request) {
  const payload = await request.text();
  let event;
  try {
    event = client.webhooks.verify(payload, request.headers, process.env.BLUEBOT_WEBHOOK_SECRET);
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response('Bad signature', { status: 400 });
    throw error;
  }

  switch (event.type) {
    case 'message.received':
      // event.data is the message.
      console.log(`${event.data.from}: ${event.data.text ?? '(a picture)'}`);
      break;
    case 'message.failed':
      console.log(`${event.data.id} failed: ${event.data.failure?.reason}`);
      break;
  }
  return new Response(null, { status: 204 });
}
```

```ts title="Express"
import express from 'express';
import BlueBot, { WebhookVerificationError } from '@usebluebot/sdk';

const app = express();
const client = new BlueBot();

// express.raw keeps the body as bytes; express.json() would break the signature.
app.post('/bluebot/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = client.webhooks.verify(req.body, req.headers, process.env.BLUEBOT_WEBHOOK_SECRET);
    if (event.type === 'contact.stopped') console.log(`${event.data.handle} stopped`);
    res.sendStatus(204);
  } catch (error) {
    if (error instanceof WebhookVerificationError) return res.sendStatus(400);
    throw error;
  }
});
```

Answer quickly with any 2xx and do slow work afterwards; BlueBot waits 10 seconds for an answer. The same event can arrive twice, so skip an `event.id` you’ve already handled.

To verify without a client or an API key, such as in a function that only receives webhooks, import `verifyWebhook`, which takes the same arguments: `verifyWebhook(payload, headers, secret)`.

## Idempotency and retries

Every POST carries an `Idempotency-Key`. The SDK makes one when you don’t pass it and reuses it for that call’s retries. Pass your own when your code might run the same send twice, such as a job queue that can deliver a job again:

```ts
await client.messages.send(
  { to: lead.phone, text: `Hi ${lead.firstName}, thanks for your interest.` },
  { idempotencyKey: `welcome-${lead.id}` },
);
```

For 24 hours, the same key with the same request answers the first text again; the same key with a different request is a `ConflictError` with code `idempotency_conflict`.

The SDK retries a call (twice by default) after a connection failure or timeout, a 429, a 5xx, or a 409 that carries `Retry-After`. It waits what BlueBot asks, or backs off from half a second to at most 8 seconds. [SDKs](https://usebluebot.com/docs/sdks#retries) has the details.

### Request options

Every method takes one last, optional argument:

| Option           | Type                     | What it does                            |
| ---------------- | ------------------------ | --------------------------------------- |
| `idempotencyKey` | `string`                 | The `Idempotency-Key` for this POST.    |
| `timeout`        | `number`                 | Milliseconds for each try of this call. |
| `maxRetries`     | `number`                 | Retries for this call.                  |
| `signal`         | `AbortSignal`            | Cancels the call, retries included.     |
| `headers`        | `Record<string, string>` | Extra headers for this call.            |

A method with no body or query takes the options right after its path parameters: `client.messages.cancel(id, { idempotencyKey: 'cancel-9921' })`.

### The response itself

Add `.withResponse()` to any call to get the HTTP response and request id along with the record:

```ts
const { data: message, response, requestId } = await client.messages
  .send({ to: '+15555550123', text: 'Hello again.' }, { idempotencyKey: 'hello-again-1' })
  .withResponse();

console.log(requestId, response.headers.get('Idempotent-Replayed')); // 'true' when BlueBot answered an earlier send
console.log(response.headers.get('RateLimit-Remaining'));
```

## Errors

```ts
import BlueBot, { APIError, BadRequestError, RateLimitError, APIConnectionError } from '@usebluebot/sdk';

try {
  await client.messages.send({ to: '555', text: 'Hi' });
} catch (error) {
  if (error instanceof BadRequestError) {
    for (const { field, message } of error.details ?? []) console.log(`${field}: ${message}`);
  } else if (error instanceof RateLimitError) {
    console.log(`Slow down; try again in ${error.retryAfter} seconds`);
  } else if (error instanceof APIError) {
    console.error(`${error.code}: ${error.message} ${error.hint} (${error.requestId})`);
  } else if (error instanceof APIConnectionError) {
    console.error('BlueBot could not be reached');
  } else {
    throw error;
  }
}
```

Every `APIError` has `status`, `code`, `message`, `hint`, `docUrl`, `requestId`, `retryAfter`, `details` and `headers`. Branch on `code`, never on `message`. The classes are `BadRequestError` (400), `AuthenticationError` (401), `PlanLapsedError` (402), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `PayloadTooLargeError` (413), `UnsupportedMediaTypeError` (415), `RateLimitError` (429) and `InternalServerError` (5xx), all extending `APIError`, which extends `BlueBotError`. `APIConnectionError` (and its subclass `APITimeoutError`) and `WebhookVerificationError` extend `BlueBotError` too. [Errors](https://usebluebot.com/docs/errors) has a page for each code.

## Types

Everything the SDK takes and answers is exported as a type:

```ts
import type {
  Message, MessageState, Hold, HoldReason, FileObject, Chat, Contact, Stop, Lookup, Device, Status, Me, Settings,
  Webhook, WebhookWithSecret, WebhookDelivery, WebhookEvent, MessageReceivedEvent, ContactStoppedEvent,
  MessageSendParams, MessageListParams, Page, RequestOptions,
} from '@usebluebot/sdk';
```

Records are named after the API’s objects (`FileObject` for a file, to stay clear of the DOM’s `File`). Each event type is `<Type>Event` in PascalCase (`MessageReceivedEvent`, `DeviceCheckFailedEvent`), and `WebhookEvent` is the union of them all, so a `switch` on `event.type` narrows `event.data`. Each method’s argument is `<Resource><Method>Params`, such as `WebhookCreateParams`. The types are generated from the [OpenAPI file](https://usebluebot.com/docs/openapi), so they match the API exactly.

## Logging

Set `BLUEBOT_LOG=debug` to log each request’s method, path, status, duration and request id to standard error, or `BLUEBOT_LOG=info` for retries and errors only. Bodies are never logged, so the words of texts and your key stay out of your logs.

## Testing your code

Point the client at a stand-in with the `fetch` option, and turn retries off so a failure shows at once:

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({
  apiKey: 'bb_test_unused',
  maxRetries: 0,
  fetch: async (url, init) =>
    Response.json({ id: 'msg_1Hq2LxW9cVbN3mKpRtYs0a', object: 'message', state: 'queued' /* … */ }, { status: 201 }),
});
```

A `bb_test_` key works only against a BlueBot running on your own machine, never against `usebluebot.com`, so a test can’t text anybody by mistake.

## Every method

Every API operation and the method that calls it. The Python SDK’s name is beside each.

### Messages

| TypeScript                  | Python                      | Calls                                                                  |
| --------------------------- | --------------------------- | ---------------------------------------------------------------------- |
| `client.messages.list()`    | `client.messages.list()`    | [`GET /v1/messages`](https://usebluebot.com/docs/api/messages/list-messages)                 |
| `client.messages.send()`    | `client.messages.send()`    | [`POST /v1/messages`](https://usebluebot.com/docs/api/messages/send-message)                 |
| `client.messages.get()`     | `client.messages.get()`     | [`GET /v1/messages/{id}`](https://usebluebot.com/docs/api/messages/get-message)              |
| `client.messages.update()`  | `client.messages.update()`  | [`PATCH /v1/messages/{id}`](https://usebluebot.com/docs/api/messages/update-message)         |
| `client.messages.cancel()`  | `client.messages.cancel()`  | [`POST /v1/messages/{id}/cancel`](https://usebluebot.com/docs/api/messages/cancel-message)   |
| `client.messages.resolve()` | `client.messages.resolve()` | [`POST /v1/messages/{id}/resolve`](https://usebluebot.com/docs/api/messages/resolve-message) |

### Files

| TypeScript              | Python                  | Calls                                            |
| ----------------------- | ----------------------- | ------------------------------------------------ |
| `client.files.upload()` | `client.files.upload()` | [`POST /v1/files`](https://usebluebot.com/docs/api/files/upload-file)  |
| `client.files.get()`    | `client.files.get()`    | [`GET /v1/files/{id}`](https://usebluebot.com/docs/api/files/get-file) |

### Chats

| TypeScript            | Python                | Calls                                            |
| --------------------- | --------------------- | ------------------------------------------------ |
| `client.chats.list()` | `client.chats.list()` | [`GET /v1/chats`](https://usebluebot.com/docs/api/chats/list-chats)    |
| `client.chats.get()`  | `client.chats.get()`  | [`GET /v1/chats/{id}`](https://usebluebot.com/docs/api/chats/get-chat) |

### Lookup

| TypeScript        | Python            | Calls                                              |
| ----------------- | ----------------- | -------------------------------------------------- |
| `client.lookup()` | `client.lookup()` | [`GET /v1/lookup`](https://usebluebot.com/docs/api/lookup/lookup-number) |

### Device

| TypeScript            | Python                | Calls                                           |
| --------------------- | --------------------- | ----------------------------------------------- |
| `client.device.get()` | `client.device.get()` | [`GET /v1/device`](https://usebluebot.com/docs/api/device/get-device) |
| `client.status()`     | `client.status()`     | [`GET /v1/status`](https://usebluebot.com/docs/api/device/get-status) |

### Settings

| TypeScript                 | Python                     | Calls                                                      |
| -------------------------- | -------------------------- | ---------------------------------------------------------- |
| `client.settings.get()`    | `client.settings.get()`    | [`GET /v1/settings`](https://usebluebot.com/docs/api/settings/get-settings)      |
| `client.settings.update()` | `client.settings.update()` | [`PATCH /v1/settings`](https://usebluebot.com/docs/api/settings/update-settings) |

### Stops

| TypeScript              | Python                  | Calls                                                      |
| ----------------------- | ----------------------- | ---------------------------------------------------------- |
| `client.stops.list()`   | `client.stops.list()`   | [`GET /v1/stops`](https://usebluebot.com/docs/api/stops/list-stops)              |
| `client.stops.add()`    | `client.stops.add()`    | [`POST /v1/stops`](https://usebluebot.com/docs/api/stops/add-stop)               |
| `client.stops.remove()` | `client.stops.remove()` | [`DELETE /v1/stops/{person}`](https://usebluebot.com/docs/api/stops/remove-stop) |

### Events

| TypeScript             | Python                 | Calls                                               |
| ---------------------- | ---------------------- | --------------------------------------------------- |
| `client.events.list()` | `client.events.list()` | [`GET /v1/events`](https://usebluebot.com/docs/api/events/list-events)    |
| `client.events.get()`  | `client.events.get()`  | [`GET /v1/events/{id}`](https://usebluebot.com/docs/api/events/get-event) |

### Webhooks

| TypeScript                            | Python                                | Calls                                                                                          |
| ------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `client.webhooks.list()`              | `client.webhooks.list()`              | [`GET /v1/webhooks`](https://usebluebot.com/docs/api/webhooks/list-webhooks)                                         |
| `client.webhooks.create()`            | `client.webhooks.create()`            | [`POST /v1/webhooks`](https://usebluebot.com/docs/api/webhooks/create-webhook)                                       |
| `client.webhooks.get()`               | `client.webhooks.get()`               | [`GET /v1/webhooks/{id}`](https://usebluebot.com/docs/api/webhooks/get-webhook)                                      |
| `client.webhooks.update()`            | `client.webhooks.update()`            | [`PATCH /v1/webhooks/{id}`](https://usebluebot.com/docs/api/webhooks/update-webhook)                                 |
| `client.webhooks.delete()`            | `client.webhooks.delete()`            | [`DELETE /v1/webhooks/{id}`](https://usebluebot.com/docs/api/webhooks/delete-webhook)                                |
| `client.webhooks.test()`              | `client.webhooks.test()`              | [`POST /v1/webhooks/{id}/test`](https://usebluebot.com/docs/api/webhooks/test-webhook)                               |
| `client.webhooks.rotateSecret()`      | `client.webhooks.rotate_secret()`     | [`POST /v1/webhooks/{id}/rotate-secret`](https://usebluebot.com/docs/api/webhooks/rotate-webhook-secret)             |
| `client.webhooks.deliveries.list()`   | `client.webhooks.deliveries.list()`   | [`GET /v1/webhooks/{id}/deliveries`](https://usebluebot.com/docs/api/webhooks/list-deliveries)                       |
| `client.webhooks.deliveries.replay()` | `client.webhooks.deliveries.replay()` | [`POST /v1/webhooks/{id}/deliveries/{delivery_id}/replay`](https://usebluebot.com/docs/api/webhooks/replay-delivery) |

### Account

| TypeScript    | Python        | Calls                                    |
| ------------- | ------------- | ---------------------------------------- |
| `client.me()` | `client.me()` | [`GET /v1/me`](https://usebluebot.com/docs/api/account/get-me) |

### Conversations

| TypeScript                             | Python                                 | Calls                                                                                       |
| -------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
| `client.conversations.list()`          | `client.conversations.list()`          | [`GET /v1/conversations`](https://usebluebot.com/docs/api/conversations/list-conversations)                       |
| `client.conversations.get()`           | `client.conversations.get()`           | [`GET /v1/conversations/{id}`](https://usebluebot.com/docs/api/conversations/get-conversation)                    |
| `client.conversations.update()`        | `client.conversations.update()`        | [`PATCH /v1/conversations/{id}`](https://usebluebot.com/docs/api/conversations/update-conversation)               |
| `client.conversations.messages.list()` | `client.conversations.messages.list()` | [`GET /v1/conversations/{id}/messages`](https://usebluebot.com/docs/api/conversations/list-conversation-messages) |

### Analytics

| TypeScript               | Python                   | Calls                                                    |
| ------------------------ | ------------------------ | -------------------------------------------------------- |
| `client.analytics.get()` | `client.analytics.get()` | [`GET /v1/analytics`](https://usebluebot.com/docs/api/analytics/get-analytics) |

### Contacts

| TypeScript                 | Python                     | Calls                                                           |
| -------------------------- | -------------------------- | --------------------------------------------------------------- |
| `client.contacts.list()`   | `client.contacts.list()`   | [`GET /v1/contacts`](https://usebluebot.com/docs/api/contacts/list-contacts)          |
| `client.contacts.create()` | `client.contacts.create()` | [`POST /v1/contacts`](https://usebluebot.com/docs/api/contacts/create-contact)        |
| `client.contacts.get()`    | `client.contacts.get()`    | [`GET /v1/contacts/{id}`](https://usebluebot.com/docs/api/contacts/get-contact)       |
| `client.contacts.update()` | `client.contacts.update()` | [`PATCH /v1/contacts/{id}`](https://usebluebot.com/docs/api/contacts/update-contact)  |
| `client.contacts.delete()` | `client.contacts.delete()` | [`DELETE /v1/contacts/{id}`](https://usebluebot.com/docs/api/contacts/delete-contact) |

### Templates

| TypeScript                  | Python                      | Calls                                                              |
| --------------------------- | --------------------------- | ------------------------------------------------------------------ |
| `client.templates.list()`   | `client.templates.list()`   | [`GET /v1/templates`](https://usebluebot.com/docs/api/templates/list-templates)          |
| `client.templates.create()` | `client.templates.create()` | [`POST /v1/templates`](https://usebluebot.com/docs/api/templates/create-template)        |
| `client.templates.get()`    | `client.templates.get()`    | [`GET /v1/templates/{id}`](https://usebluebot.com/docs/api/templates/get-template)       |
| `client.templates.update()` | `client.templates.update()` | [`PATCH /v1/templates/{id}`](https://usebluebot.com/docs/api/templates/update-template)  |
| `client.templates.delete()` | `client.templates.delete()` | [`DELETE /v1/templates/{id}`](https://usebluebot.com/docs/api/templates/delete-template) |

### Broadcasts

| TypeScript                            | Python                                | Calls                                                                                  |
| ------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------- |
| `client.broadcasts.list()`            | `client.broadcasts.list()`            | [`GET /v1/broadcasts`](https://usebluebot.com/docs/api/broadcasts/list-broadcasts)                           |
| `client.broadcasts.create()`          | `client.broadcasts.create()`          | [`POST /v1/broadcasts`](https://usebluebot.com/docs/api/broadcasts/create-broadcast)                         |
| `client.broadcasts.preview()`         | `client.broadcasts.preview()`         | [`POST /v1/broadcasts/preview`](https://usebluebot.com/docs/api/broadcasts/preview-broadcast)                |
| `client.broadcasts.get()`             | `client.broadcasts.get()`             | [`GET /v1/broadcasts/{id}`](https://usebluebot.com/docs/api/broadcasts/get-broadcast)                        |
| `client.broadcasts.recipients.list()` | `client.broadcasts.recipients.list()` | [`GET /v1/broadcasts/{id}/recipients`](https://usebluebot.com/docs/api/broadcasts/list-broadcast-recipients) |
| `client.broadcasts.cancel()`          | `client.broadcasts.cancel()`          | [`POST /v1/broadcasts/{id}/cancel`](https://usebluebot.com/docs/api/broadcasts/cancel-broadcast)             |

### Sequences

| TypeScript                              | Python                                  | Calls                                                                           |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------- |
| `client.sequences.list()`               | `client.sequences.list()`               | [`GET /v1/sequences`](https://usebluebot.com/docs/api/sequences/list-sequences)                       |
| `client.sequences.create()`             | `client.sequences.create()`             | [`POST /v1/sequences`](https://usebluebot.com/docs/api/sequences/create-sequence)                     |
| `client.sequences.get()`                | `client.sequences.get()`                | [`GET /v1/sequences/{id}`](https://usebluebot.com/docs/api/sequences/get-sequence)                    |
| `client.sequences.update()`             | `client.sequences.update()`             | [`PATCH /v1/sequences/{id}`](https://usebluebot.com/docs/api/sequences/update-sequence)               |
| `client.sequences.enrollments.list()`   | `client.sequences.enrollments.list()`   | [`GET /v1/sequences/{id}/enrollments`](https://usebluebot.com/docs/api/sequences/list-enrollments)    |
| `client.sequences.enrollments.create()` | `client.sequences.enrollments.create()` | [`POST /v1/sequences/{id}/enrollments`](https://usebluebot.com/docs/api/sequences/enroll-in-sequence) |
| `client.enrollments.get()`              | `client.enrollments.get()`              | [`GET /v1/enrollments/{id}`](https://usebluebot.com/docs/api/sequences/get-enrollment)                |
| `client.enrollments.cancel()`           | `client.enrollments.cancel()`           | [`POST /v1/enrollments/{id}/cancel`](https://usebluebot.com/docs/api/sequences/cancel-enrollment)     |

### Text Hooks

| TypeScript              | Python                  | Calls                                                            |
| ----------------------- | ----------------------- | ---------------------------------------------------------------- |
| `client.hooks.list()`   | `client.hooks.list()`   | [`GET /v1/hooks`](https://usebluebot.com/docs/api/text-hooks/list-text-hooks)          |
| `client.hooks.create()` | `client.hooks.create()` | [`POST /v1/hooks`](https://usebluebot.com/docs/api/text-hooks/create-text-hook)        |
| `client.hooks.get()`    | `client.hooks.get()`    | [`GET /v1/hooks/{id}`](https://usebluebot.com/docs/api/text-hooks/get-text-hook)       |
| `client.hooks.update()` | `client.hooks.update()` | [`PATCH /v1/hooks/{id}`](https://usebluebot.com/docs/api/text-hooks/update-text-hook)  |
| `client.hooks.delete()` | `client.hooks.delete()` | [`DELETE /v1/hooks/{id}`](https://usebluebot.com/docs/api/text-hooks/delete-text-hook) |

### Automations

| TypeScript                          | Python                              | Calls                                                                            |
| ----------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------- |
| `client.automations.rules.list()`   | `client.automations.rules.list()`   | [`GET /v1/automations/rules`](https://usebluebot.com/docs/api/automations/list-keyword-rules)          |
| `client.automations.rules.create()` | `client.automations.rules.create()` | [`POST /v1/automations/rules`](https://usebluebot.com/docs/api/automations/create-keyword-rule)        |
| `client.automations.rules.get()`    | `client.automations.rules.get()`    | [`GET /v1/automations/rules/{id}`](https://usebluebot.com/docs/api/automations/get-keyword-rule)       |
| `client.automations.rules.update()` | `client.automations.rules.update()` | [`PATCH /v1/automations/rules/{id}`](https://usebluebot.com/docs/api/automations/update-keyword-rule)  |
| `client.automations.rules.delete()` | `client.automations.rules.delete()` | [`DELETE /v1/automations/rules/{id}`](https://usebluebot.com/docs/api/automations/delete-keyword-rule) |
| `client.automations.away.get()`     | `client.automations.away.get()`     | [`GET /v1/automations/away`](https://usebluebot.com/docs/api/automations/get-away-message)             |
| `client.automations.away.update()`  | `client.automations.away.update()`  | [`PATCH /v1/automations/away`](https://usebluebot.com/docs/api/automations/update-away-message)        |
