# Python SDK

> The bluebot package for Python 3.9 and newer, sync and async on httpx with pydantic models. The client, every method, pagination, idempotency and retries, typed errors and webhook verification.

`bluebot` is BlueBot’s official Python SDK. It has a synchronous client, `BlueBot`, and an asynchronous one, `AsyncBlueBot`, with one method for each API operation. Answers are pydantic models, fully typed for your editor and for mypy or pyright, and the SDK handles idempotency keys, retries, pagination and webhook signatures for you.

## Install

```bash title="pip"
pip install bluebot
```

```bash title="uv"
uv add bluebot
```

```bash title="poetry"
poetry add bluebot
```

It needs Python 3.9 or newer and brings `httpx` and `pydantic` (version 2).

## Make a client

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])
```

| Argument          | Type                                                    | Default                                                  | What it does                                                                 |
| ----------------- | ------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `api_key`         | `str`                                                   | `BLUEBOT_API_KEY` from the environment                   | Your `bb_live_…` key. The constructor raises `BlueBotError` if there’s none. |
| `base_url`        | `str`                                                   | `BLUEBOT_BASE_URL`, else `https://usebluebot.com/api/v1` | Change it only to reach a BlueBot you run locally.                           |
| `timeout`         | `float` or `httpx.Timeout`                              | `60.0`                                                   | Seconds to wait for each try.                                                |
| `max_retries`     | `int`                                                   | `2`                                                      | Retries after the first try, for what is safe to retry.                      |
| `default_headers` | `dict[str, str]`                                        | none                                                     | Sent on every request.                                                       |
| `http_client`     | `httpx.Client` (`httpx.AsyncClient` for `AsyncBlueBot`) | one of its own                                           | Pass your own client to use a proxy or a custom transport.                   |

Make one client and share it: it keeps a connection pool. Close it when you’re done, or use it as a context manager:

```python
with BlueBot() as client:
    client.messages.send(to="+15555550123", text="Your table for four is ready.")
```

`client.with_options(timeout=10, max_retries=0)` returns a copy with some options changed, sharing the connection pool.

## Send a text

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

print(message.id, message.state)  # msg_1Hq2LxW9cVbN3mKpRtYs0a queued
if message.hold:
    print(f"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. Use [webhooks](https://usebluebot.com/docs/webhooks) to learn when it was sent, delivered or read.

## Models

Answers are pydantic models with the API’s field names in snake_case, so `message.send_at`, `message.chat_id` and `page.next_cursor` read as the API spells them. Two things differ from the JSON:

- `from` is a reserved word in Python, so it’s `message.from_`. Everything else keeps its name.
- Times are `datetime` objects in UTC, and empty fields are `None`.

To get the API’s JSON back, with `from` spelled as it is in the API:

```python
data = message.model_dump(mode="json", by_alias=True)
```

`metadata` is a plain `dict[str, str]`, returned exactly as you sent it. Times you pass may be a `datetime` with a time zone or an ISO 8601 string. A `datetime` without a zone raises `ValueError` before anything is sent, since BlueBot can’t guess which one you meant.

## Messages

```python
from datetime import datetime, timedelta, timezone

# Schedule one. It stays a draft you can change until it's due.
reminder = client.messages.send(
    to="+15555550123",
    text="Reminder: your cleaning is tomorrow at 10 AM. Reply C to confirm.",
    send_at=datetime(2026, 11, 20, 15, 0, tzinfo=timezone.utc),
    metadata={"appointment_id": "apt_9921"},
)

# Move it, or change the words, before it goes.
client.messages.update(reminder.id, send_at="2026-11-21T15:00:00Z")

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

# Text an existing group chat instead of one person.
client.messages.send(chat_id="chat_1Hq2LxW9cVbN3mKpRtYs0a", text="Running 10 minutes late.")

# Insist on iMessage (no SMS fallback), and give up if it hasn't gone in an hour.
client.messages.send(
    to="maria@example.com",
    text="Here is the link you asked for.",
    service="imessage",
    expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
)

# Read one.
sent = client.messages.get("msg_1Hq2LxW9cVbN3mKpRtYs0a")

# Settle a text the Mac couldn't confirm: you checked and it went, or send it again.
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 first, then send the file’s id. A file nobody sends is deleted after a day.

```python
from pathlib import Path

photo = client.files.upload(Path("house.jpg"))
client.messages.send(to="+15555550123", text="Here is the front of the house.", files=[photo.id])
```

`upload` takes a `pathlib.Path`, an open binary file (`open("house.jpg", "rb")`), `bytes` with a `filename=`, or a `(filename, content, content_type)` tuple. A file another app gives you as a URL:

```python
import httpx

flyer = httpx.get("https://example.com/flyer.png")
upload = client.files.upload(flyer.content, filename="flyer.png")
```

Files that arrive on a text are on `message.files`, each with a `url` that works for an hour while its `status` is `"ready"`; `client.files.get(file_id)` gives a fresh one. A voice memo may carry a `transcript`.

## Lists and pagination

Every list call answers a page, newest first. Use `.data` for that page, or loop over the call to walk every item:

```python
# One page.
page = client.messages.list(direction="in", limit=50)
print(len(page.data), page.has_more, page.next_cursor)

# Every item, page after page.
for message in client.messages.list(state="failed", since="2026-11-01T00:00:00Z"):
    print(message.to, message.failure.reason if message.failure else None)

# By hand.
page = client.chats.list(is_group=True)
while True:
    for chat in page.data:
        print(chat.name)
    if not page.has_next_page():
        break
    page = page.get_next_page()
```

`limit` is 1 to 100, 25 by default. A loop fetches the next page only when it reaches it, and fetches nothing more after you `break`.

## Lookup, the Mac and your account

```python
lookup = client.lookup("+15555550123")
print(lookup.service, lookup.basis)  # imessage history

device = client.device.get()
if device.status != "online":
    print(f"Offline since {device.offline_since}")
for name, check in device.checks:
    if check.state == "needs_you":
        print(f"{name}: {check.detail}")
print(f"{device.today.new_people} of {device.today.new_people_limit} new people today")

status = client.status()
me = client.me()
print(status.sending, me.account.email, me.key.scopes if me.key else None)
```

A lookup of a number you’ve never texted answers `service="unknown"`; Apple offers no way to ask.

## Settings and stops

```python
settings = client.settings.update(
    time_zone="America/Chicago",
    pace_seconds=30,
    quiet_hours={"enabled": True, "start": "20:00", "end": "08:00"},
)
print(settings.warnings)

# Pause all sending for two hours, then resume.
client.settings.update(paused_until=datetime.now(timezone.utc) + timedelta(hours=2))
client.settings.update(paused_until=None)

client.stops.add(person="+15555550144")
for stop in client.stops.list():
    print(stop.handle, stop.source)
client.stops.remove("+15555550144")  # the SDK encodes the + for the path
```

## Events

```python
for event in client.events.list(type=["message.received", "message.failed"], since="2026-11-12T00:00:00Z"):
    print(event.type, event.timestamp)

event = client.events.get("evt_1Hq2LxW9cVbN3mKpRtYs0a")
```

`type` takes a comma-separated string or a list.

## Webhooks

```python
webhook = client.webhooks.create(
    url="https://example.com/bluebot/webhooks",
    events=["message.received", "message.failed"],
    description="Replies into the CRM",
)
print(webhook.secret)  # shown now and on rotation, never again: store it

client.webhooks.test(webhook.id, type="message.received")
client.webhooks.update(webhook.id, paused=True)
secret = client.webhooks.rotate_secret(webhook.id).secret

for delivery in client.webhooks.deliveries.list(webhook.id, state="gave_up"):
    client.webhooks.deliveries.replay(webhook.id, delivery.id)

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

### Verify what BlueBot sends

`client.webhooks.verify(payload, headers, secret=None, tolerance=300)` checks a delivery’s Standard Webhooks signature and timestamp and returns the event as a model. Pass the raw body, as `bytes` or `str`, exactly as it arrived. `secret` defaults to `BLUEBOT_WEBHOOK_SECRET` from the environment. It raises `bluebot.WebhookVerificationError` when a header is missing, the timestamp is more than `tolerance` seconds from now, no signature matches, or the body isn’t JSON. During a secret rotation a delivery carries two signatures, and either passes.

```python title="Flask"
import os
from flask import Flask, abort, request
from bluebot import BlueBot, WebhookVerificationError

app = Flask(__name__)
client = BlueBot()

@app.post("/bluebot/webhooks")
def bluebot_webhook():
    try:
        event = client.webhooks.verify(
            request.get_data(),  # the raw body, byte for byte
            request.headers,
            secret=os.environ["BLUEBOT_WEBHOOK_SECRET"],
        )
    except WebhookVerificationError:
        abort(400)

    if event.type == "message.received":
        message = event.data  # the message
        print(f"{message.from_}: {message.text}")
    return "", 204
```

```python title="FastAPI"
import os
from fastapi import FastAPI, HTTPException, Request, Response
from bluebot import BlueBot, WebhookVerificationError

app = FastAPI()
client = BlueBot()

@app.post("/bluebot/webhooks")
async def bluebot_webhook(request: Request):
    try:
        event = client.webhooks.verify(await request.body(), request.headers, secret=os.environ["BLUEBOT_WEBHOOK_SECRET"])
    except WebhookVerificationError:
        raise HTTPException(status_code=400)

    if event.type == "contact.stopped":
        print(f"{event.data.handle} stopped")
    return Response(status_code=204)
```

`verify` does no I/O, so it is the same plain method on `AsyncBlueBot`. To verify without a client or an API key, use `from bluebot import verify_webhook` with the same arguments. Answer within 10 seconds, and skip an `event.id` you’ve already handled, since the same event can arrive twice.

## Async

`AsyncBlueBot` has the same methods, each a coroutine. Lists are awaitable for one page and async-iterable for every item:

```python
import asyncio
from bluebot import AsyncBlueBot

async def main():
    async with AsyncBlueBot() as client:
        message = await client.messages.send(to="+15555550123", text="Your order shipped today.")
        print(message.state)

        async for received in client.messages.list(direction="in", limit=100):
            print(received.from_, received.text)

        page = await client.messages.list(state="failed")
        print(len(page.data))

asyncio.run(main())
```

Firing many sends at once doesn’t make texts go faster: BlueBot paces texts from your Mac (20 seconds apart by default) whatever order they arrive in.

## 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 task queue that can deliver a task again:

```python
client.messages.send(
    to=lead.phone,
    text=f"Hi {lead.first_name}, thanks for your interest.",
    idempotency_key=f"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 raises `ConflictError` with `code == "idempotency_conflict"`. The SDK retries (twice by default) after a connection failure or timeout, a 429, a 5xx, or a 409 that carries `Retry-After`, waiting what BlueBot asks or backing off from half a second to at most 8 seconds.

Every method also takes these keyword arguments:

| Argument          | What it does                         |
| ----------------- | ------------------------------------ |
| `idempotency_key` | The `Idempotency-Key` for this POST. |
| `timeout`         | Seconds for each try of this call.   |
| `max_retries`     | Retries for this call.               |
| `extra_headers`   | Extra headers for this call.         |

### The response itself

`with_raw_response` before a method answers the HTTP response, with the record a `.parse()` away:

```python
raw = client.messages.with_raw_response.send(to="+15555550123", text="Hello again.", idempotency_key="hello-again-1")
print(raw.headers.get("Request-Id"), raw.headers.get("Idempotent-Replayed"))
message = raw.parse()
```

## Errors

```python
import bluebot

try:
    client.messages.send(to="555", text="Hi")
except bluebot.BadRequestError as error:
    for detail in error.details or []:
        print(f"{detail.field}: {detail.message}")
except bluebot.RateLimitError as error:
    print(f"Slow down; try again in {error.retry_after} seconds")
except bluebot.APIError as error:
    print(f"{error.code}: {error.message} {error.hint} ({error.request_id})")
except bluebot.APIConnectionError:
    print("BlueBot could not be reached")
```

Every `APIError` has `status`, `code`, `message`, `hint`, `doc_url`, `request_id`, `retry_after`, `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 subclasses of `APIError`, which subclasses `BlueBotError`. `APIConnectionError` (and its subclass `APITimeoutError`) and `WebhookVerificationError` subclass `BlueBotError` too.

## Types

The models are importable for annotations:

```python
from bluebot.types import Message, Hold, FileObject, Chat, Contact, Lookup, Device, Settings, Webhook, WebhookDelivery
from bluebot.types import WebhookEvent, MessageReceivedEvent, ContactStoppedEvent
```

Each event type is `<Type>Event` (`MessageReceivedEvent`, `DeviceCheckFailedEvent`), and `WebhookEvent` is their union, discriminated on `type`. A file is `FileObject`, as in TypeScript. The models are generated from the [OpenAPI file](https://usebluebot.com/docs/openapi). The SDK builds them from BlueBot’s answers without validating, so a field it doesn’t know, or one that’s missing, never raises, and an older SDK keeps working when the API adds a field.

## Logging

Set `BLUEBOT_LOG=debug` to log each request’s method, path, status, duration and request id through the standard `logging` module (logger `bluebot`), or `BLUEBOT_LOG=info` for retries and errors only. The path is logged as the API writes it, such as `DELETE /stops/{person}`, so a phone number or an id in it stays out. Bodies are never logged, so the words of texts and your key stay out of your logs too.

## Testing your code

The SDK sends everything through `httpx`, so `respx` or `httpx.MockTransport` can stand in for BlueBot:

```python
import httpx
from bluebot import BlueBot

def handler(request: httpx.Request) -> httpx.Response:
    return httpx.Response(201, json={"id": "msg_1Hq2LxW9cVbN3mKpRtYs0a", "object": "message", "state": "queued"})

client = BlueBot(api_key="bb_test_unused", max_retries=0, http_client=httpx.Client(transport=httpx.MockTransport(handler)))
```

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, the method that calls it and the TypeScript name beside it.

### 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)        |
