> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.messageblue.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.messageblue.ai/_mcp/server.

# API reference

The iMessage Gateway API routes send jobs to registered Mac agents. Every page in
this reference is generated from the gateway's OpenAPI specification, so it always
matches what the service actually accepts.

**Base URL:** `https://api.messageblue.ai`

## Authentication

Core endpoints use **HMAC request signing**. You send your App ID in the clear and
prove possession of your App Secret by signing each request — the **App Secret is
never transmitted**.

Every signed request carries four headers:

| Header          | Value                                             |
| --------------- | ------------------------------------------------- |
| `X-App-Id`      | Your app id                                       |
| `X-Timestamp`   | Unix time in **milliseconds** at signing          |
| `X-Nonce`       | A UUID, unique per request (replays are rejected) |
| `Authorization` | `HMAC <lowercase-hex-signature>`                  |

The signature is computed over the request method, path, body hash, timestamp and
nonce:

```
bodyHash  = sha256hex( canonicalJson(body) || "" )
toSign    = [ METHOD, path, bodyHash, X-Timestamp, X-Nonce ].join("\n")
signature = HMAC_SHA256( appSecret, toSign )
```

The timestamp must be within a five-minute window of the gateway's clock.

The official TypeScript and Python SDKs sign every request for you. Reach for the
manual scheme above only if you're calling the API from another language.

## What you can do

#### Messages

Send texts, attachments and voice notes to individuals or groups, react with
tapbacks, send typing indicators and read receipts, and check whether a number
is iMessage-capable.

#### Agents

Register, assign, reassign, block and inspect the Mac agents that send on your
behalf.

#### Mappings

Inspect and manage the user⇄agent number mappings that decide which agent
handles a given contact.

#### Webhooks

Configure where inbound messages, tapbacks and delivery-status updates are
delivered, and verify their signatures.

Analytics (conversation history, contacts, per-number stats), queue status and
health endpoints round out the surface.

## Webhooks

MessageBlue delivers three events — `inbound_message`, `inbound_reaction` and
message `status` updates.

### Where they go

Register your destinations with a single call:

```bash
POST /settings/webhooks
{
  "inboundWebhookUrl": "https://example.com/webhooks/inbound",
  "statusWebhookUrl":  "https://example.com/webhooks/status"
}
```

`inboundWebhookUrl` receives both inbound events; `statusWebhookUrl` receives
delivery-status updates. Send only the field you want to change — an empty value
unsets that webhook. A per-message `status_callback` set at send time takes
precedence over `statusWebhookUrl`.

### Verifying them

**Every webhook is signed.** Each request carries:

* `X-MB-Event-Id` — a unique `evt_...` id for the event; use it to deduplicate
* `X-MB-Timestamp` — unix **seconds** when the request was signed
* `X-MB-Signature` — `v1=<hex>`, where `<hex>` is `HMAC-SHA256(secret, "{timestamp}.{rawBody}")`

Verify the signature before trusting a payload, and sign over the **raw request
bytes** — re-serializing the JSON first produces a different hash and the check
will fail. Generate your signing secret in your MessageBlue dashboard.

### Sample payloads

```json title="inbound_message"
{
  "type": "inbound_message",
  "messageId": "9c8f1e42-5b7a-4d31-8f60-2ab4c9d17e83",
  "text": "Hey — is my order still arriving today?",
  "from": "+14155550123",
  "to": "+16475137145",
  "outbound": false,
  "iMessageNumber": "+16475137145",
  "participants": ["+14155550123", "+16475137145"],
  "chatGuid": "iMessage;-;+14155550123",
  "groupId": null,
  "messageGuid": "6F1C2A54-6C1E-4E2B-9E0A-2B7C1D3E4F50",
  "attachments": [],
  "timestamp_ms": 1769472000000,
  "accountId": "acct_4f9c2b7e1d8a"
}
```

```json title="inbound_reaction"
{
  "type": "inbound_reaction",
  "messageId": "2d61f087-9a3c-4e15-b7d2-71f4c85a6039",
  "from": "+14155550123",
  "to": "+16475137145",
  "outbound": false,
  "iMessageNumber": "+16475137145",
  "participants": ["+14155550123", "+16475137145"],
  "chatGuid": "iMessage;-;+14155550123",
  "groupId": null,
  "messageGuid": "A93B7D10-4F2E-4C88-9B15-0E6A2C4D8F71",
  "originalMessageGuid": "6F1C2A54-6C1E-4E2B-9E0A-2B7C1D3E4F50",
  "originalText": "Your order ships today.",
  "reaction": "love",
  "timestamp_ms": 1769472045000,
  "accountId": "acct_4f9c2b7e1d8a"
}
```

```json title="status"
{
  "messageId": "7b3e5c19-8d24-4a70-9e31-c5f2a604b8d7",
  "status": "delivered",
  "text": "Your order ships today.",
  "from": "+16475137145",
  "to": "+14155550123",
  "groupId": null,
  "attachments": [],
  "status_callback": null,
  "outbound": true,
  "iMessageNumber": "+16475137145",
  "participants": ["+14155550123", "+16475137145"],
  "chatGuid": "iMessage;-;+14155550123",
  "messageGuid": "6F1C2A54-6C1E-4E2B-9E0A-2B7C1D3E4F50",
  "timestamp_ms": 1769472012000,
  "error": null
}
```

The `status` payload has **no `type` field** — identify it by `status`, which
progresses through values like `sent`, `delivered`, `read`, `failed` and
`SMS_FALLBACK`.

## SDKs

```bash title="npm"
npm install messageblue
```

```bash title="pip"
pip install messageblue
```

```ts title="TypeScript"
import { createMessageBlueClient } from "messageblue";

const client = createMessageBlueClient({
  appId: process.env.MB_APP_ID!,
  appSecret: process.env.MB_APP_SECRET!, // never sent — signs requests
  environment: "https://api.messageblue.ai",
});

await client.messages.sendIndividual({ to: "+16475137145", text: "Hello!" });
```

```python title="Python"
from messageblue import MessageBlue

client = MessageBlue(
    app_id="app_...",
    app_secret="sk_...",   # never sent — signs requests
)
client.messages.send_individual(to="+16475137145", text="Hello!")
```

## Trying requests

Signed endpoints have no in-page "Try it" button. Request signing has to happen
after you fill in a request body — the signature covers it — and this reference
can't do that computation, so a button here would only ever return `401`.

Use the **API Console** instead. It's served by the gateway, signs every request
with your App Secret, and covers the same endpoints:

#### [Open the API Console](https://api.messageblue.ai/docs)

Interactive, request-signing console. Enter your App ID and App Secret once and
send real requests to any endpoint.

Your App Secret stays in your browser — the console uses it to compute a signature
locally and never transmits it.

`GET /health` is the one endpoint that needs no signature, so it keeps a working
Try-it button here.