> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keebai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> @keebai/sdk — the official Keebai TypeScript client for WhatsApp. One typed client, two modes: straight to Meta Cloud API or through the Keebai platform with a PAT.

The official Keebai SDK for Node.js, TypeScript, and modern runtimes. Saves you from writing your own HTTP layer against Meta Graph API or `api.keebai.com/v1`.

<Warning>
  **Pending migration.** The published SDK still targets the per-type routes (`/v1/messages/text`, `/v1/messages/image`, …) that were replaced by the single [`POST /v1/messages`](/dev/endpoints/messages-send). Its `keebai` mode will fail against the current API until a new version ships; `meta` mode, which talks straight to Meta Cloud API, is unaffected. Call the REST endpoint directly in the meantime.
</Warning>

```ts theme={null}
import { WhatsAppClient } from "@keebai/sdk";

const wa = new WhatsAppClient({
  apiKey: process.env.KEEBAI_API_KEY!,        // kbai_pk_<hex64>
  phoneNumberId: process.env.KEEBAI_PHONE_NUMBER_ID!,
});

await wa.messages.sendText({
  to: "+15551234567",
  body: "Hello from Keebai",
});
```

That's it.

## Why use it

<CardGroup cols={2}>
  <Card title="One client, two modes" icon="arrows-split-up-and-left">
    Pass `apiKey` and it talks to Keebai with persistence, dashboards, broadcasts, and normalized webhooks. Pass `accessToken` and it goes straight to Meta Graph API. Same method surface in both modes.
  </Card>

  <Card title="Full WhatsApp coverage" icon="message">
    Text, media (image, video, audio, document, sticker), location, contact cards, reactions, mark-read, interactive (buttons, list, CTA URL, catalog), templates, and broadcasts.
  </Card>

  <Card title="Strict TypeScript" icon="code">
    Fully typed: inputs, responses, errors. Autocomplete in VS Code, Cursor, or any LSP-aware IDE. Dual ESM + CJS build with `.d.ts`.
  </Card>

  <Card title="Retries and timeouts" icon="rotate-right">
    Automatic exponential backoff on 429, 5xx, and network errors. Configurable timeout. Normalizes Keebai and Meta errors into the same class.
  </Card>
</CardGroup>

## Installation

```bash theme={null}
npm install @keebai/sdk
# or
pnpm add @keebai/sdk
# or
yarn add @keebai/sdk
```

Requires Node.js 20+ (uses global `fetch`, `FormData`, and `Blob`). Also works on edge runtimes (Vercel Edge, Cloudflare Workers, Deno, Bun) and modern browsers.

## Configuration

Set credentials via environment variables:

```bash theme={null}
export KEEBAI_API_KEY=kbai_pk_<hex64>        # from app.keebai.com → System Settings → API Keys
export KEEBAI_PHONE_NUMBER_ID=<numeric>       # phone_number_id of your WhatsApp Cloud API channel
export KEEBAI_BASE_URL=https://api.keebai.com/v1   # optional; default
```

Instantiate the client:

```ts theme={null}
import { WhatsAppClient } from "@keebai/sdk";

const wa = new WhatsAppClient({
  apiKey: process.env.KEEBAI_API_KEY!,
  phoneNumberId: process.env.KEEBAI_PHONE_NUMBER_ID!,
});
```

## The two modes

| Mode                 | Credential            | Backend                            | When                                                                                        |
| -------------------- | --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `keebai` *(default)* | `apiKey: kbai_pk_...` | `https://api.keebai.com/v1`        | You're on the Keebai platform. Persistence, multi-tenant, broadcasts, webhooks, dashboards. |
| `meta`               | `accessToken: EAA...` | `https://graph.facebook.com/v21.0` | You have your own WhatsApp Business Account and want to go straight to Meta.                |

The client infers the mode from the credential. Same method names, same return types, same error model — only the transport changes.

```ts theme={null}
// Direct to Meta
const wa = new WhatsAppClient({
  accessToken: process.env.META_ACCESS_TOKEN!,
  phoneNumberId: process.env.META_PHONE_NUMBER_ID!,
});
```

Trade-off details in [Direct Meta mode](#direct-meta-mode).

## API surface

### `client.messages.*`

| Method                          | What it does                                    |
| ------------------------------- | ----------------------------------------------- |
| `sendText`                      | Free-form text inside the 24h window.           |
| `sendImage`                     | JPG/PNG by URL or `media_id`. Optional caption. |
| `sendVideo`                     | MP4 with optional caption.                      |
| `sendAudio`                     | Audio. `voice: true` for PTT (Opus in OGG).     |
| `sendDocument`                  | PDF/DOCX/etc. with filename and caption.        |
| `sendSticker`                   | Static or animated WebP.                        |
| `sendLocation`                  | Map pin with optional name and address.         |
| `sendContacts`                  | One or more vCards.                             |
| `sendReaction`                  | Emoji on a previous message.                    |
| `markRead`                      | Mark as read, with optional typing indicator.   |
| `sendInteractiveButtons`        | Up to 3 reply buttons.                          |
| `sendInteractiveList`           | Scrollable list with sections.                  |
| `sendInteractiveCtaUrl`         | Single button with an external URL.             |
| `sendInteractiveCatalogMessage` | Catalog message (Commerce).                     |
| `sendTemplate`                  | Approved template with named variables.         |
| `sendBulk`                      | Mass broadcast (Keebai mode).                   |
| `getBulkStatus`                 | Status of a previous broadcast (Keebai mode).   |
| `sendRaw`                       | Escape hatch — raw Graph API body.              |

### `client.media.*`

| Method     | What it does                                      |
| ---------- | ------------------------------------------------- |
| `upload`   | Upload a file. Returns a reusable `media_id`.     |
| `get`      | Metadata (mime, size, sha256, signed URL).        |
| `delete`   | Delete an uploaded media asset.                   |
| `download` | Fetch the binary (`as: 'blob' \| 'arraybuffer'`). |

### `client.templates.*` *(Keebai mode)*

| Method   | What it does                            |
| -------- | --------------------------------------- |
| `list`   | List approved templates.                |
| `create` | Create and submit to Meta for approval. |
| `update` | Edit components or category.            |

## Examples

### Send an image with a caption

```ts theme={null}
await wa.messages.sendImage({
  to: "+15551234567",
  image: {
    link: "https://cdn.example.com/banner.png",
    caption: "New collection",
  },
});
```

### Template with named variables

```ts theme={null}
await wa.messages.sendTemplate({
  to: "+15551234567",
  templateName: "order_confirmation",
  language: "en_US",
  variables: {
    name: "Alex",
    order: "ORD-1234",
    amount: "$15,000",
  },
});
```

### Interactive buttons

```ts theme={null}
await wa.messages.sendInteractiveButtons({
  to: "+15551234567",
  bodyText: "Confirm your order?",
  buttons: [
    { id: "confirm", title: "Confirm" },
    { id: "cancel", title: "Cancel" },
  ],
});
```

### Upload media once, send N times

```ts theme={null}
import { readFile } from "node:fs/promises";

const buffer = await readFile("logo.png");
const file = new Blob([buffer], { type: "image/png" });

const { id: mediaId } = await wa.media.upload({
  file,
  fileName: "logo.png",
  type: "image/png",
});

for (const phone of recipients) {
  await wa.messages.sendImage({
    to: phone,
    image: { id: mediaId, caption: "Keebai logo" },
  });
}
```

### Mark as read + typing

```ts theme={null}
await wa.messages.markRead({
  messageId: "wamid.HBgL...",
  typingIndicator: "text",
});
```

### Mass broadcast

```ts theme={null}
const { broadcastId } = await wa.messages.sendBulk({
  templateName: "march_promo",
  language: "en_US",
  campaignName: "March 2026",
  recipients: [
    { to: "+15551234567", variables: { name: "Alex" } },
    { to: "+15557654321", variables: { name: "Sam" } },
  ],
});

const status = await wa.messages.getBulkStatus(broadcastId);
console.log(`${status.sent}/${status.total} sent`);
```

## Error handling

```ts theme={null}
import {
  WhatsAppApiError,
  WhatsAppValidationError,
  WhatsAppTimeoutError,
} from "@keebai/sdk";

try {
  await wa.messages.sendText({ to: "+15551234567", body: "hello" });
} catch (err) {
  if (err instanceof WhatsAppApiError) {
    console.log(err.status, err.code, err.message);
  }
}
```

Common codes:

| Code                       | Status | Cause                                            |
| -------------------------- | ------ | ------------------------------------------------ |
| `UNAUTHORIZED`             | 401    | Token revoked or invalid.                        |
| `FORBIDDEN`                | 403    | Token doesn't have the required scope.           |
| `CHANNEL_NOT_FOUND`        | 404    | `phoneNumberId` unknown in your company.         |
| `invalid_phone`            | 422    | `to` is not in E.164 format.                     |
| `MEDIA_REFERENCE_REQUIRED` | 400    | Media is missing both `link` and `media_id`.     |
| `THROTTLED`                | 429    | Hit the rate limit (the SDK retries on its own). |

429s, 5xx, and network errors retry automatically with exponential backoff. Configurable:

```ts theme={null}
new WhatsAppClient({
  apiKey: "kbai_pk_...",
  phoneNumberId: "100000000000001",
  timeoutMs: 30_000,
  retries: 2,                              // default
});
```

## Direct Meta mode

When you'd rather go straight to Meta without the platform:

```ts theme={null}
const wa = new WhatsAppClient({
  accessToken: process.env.META_ACCESS_TOKEN!,
  phoneNumberId: process.env.META_PHONE_NUMBER_ID!,
});
```

| Concern                 | `keebai` mode                                                   | `meta` mode                                          |
| ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------- |
| Auth                    | PAT (`kbai_pk_...`) from app.keebai.com                         | Access token (`EAA...`) from developers.facebook.com |
| Persistence             | Yes — conversations, messages, contacts                         | No — Meta doesn't store history                      |
| Normalized webhooks     | Yes — `{ event, data, timestamp }` envelope with HMAC signature | No — raw Meta envelope                               |
| Broadcasts (`sendBulk`) | Yes                                                             | Not available                                        |
| Templates CRUD          | Yes                                                             | Not in this SDK; use the Business Management API     |
| Dashboard               | Yes                                                             | No                                                   |
| Multi-tenant            | One PAT, multiple channels                                      | One token per WABA                                   |
| Send messages           | Full coverage                                                   | Full coverage                                        |

## Helpers

```ts theme={null}
import { buildTemplateSendPayload, normalizePhone } from "@keebai/sdk";

const template = buildTemplateSendPayload({
  name: "welcome",
  language: "en_US",
  body: [{ type: "text", text: "Alex", parameterName: "name" }],
  buttons: [{ subType: "quick_reply", index: 0, payload: "GO" }],
});

const phone = normalizePhone("(555) 123-4567"); // "+15551234567"
```

## Resources

<CardGroup cols={2}>
  <Card title="GitHub repo" icon="github" href="https://github.com/keebai-ai/keebai-sdk">
    Source code, issues, and PRs. MIT.
  </Card>

  <Card title="npm: @keebai/sdk" icon="npm" href="https://www.npmjs.com/package/@keebai/sdk">
    Latest version, changelog, install stats.
  </Card>

  <Card title="Agent Skills" icon="robot" href="/dev/sdks/agent-skills">
    The `integrate-keebai-whatsapp` skill for AI agents, built on this SDK.
  </Card>

  <Card title="REST API" icon="book" href="/dev/overview">
    If you'd rather call the API directly without the SDK.
  </Card>
</CardGroup>
