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

# Changelog

Notable changes to Dial — new capabilities, improvements, and fixes. Filter by tag, or subscribe via [RSS](/documentation/changelog.rss).

## November 12, 2026

## Added

### A published versioning and deprecation policy

The REST API now has a written [versioning and deprecation policy](/documentation/reference/versioning): breaking changes ship under a new path version, and anything deprecated in `/v1` keeps working for at least 6 months, announced with `Deprecation` and `Sunset` response headers.

### `Location` header on asynchronous provisioning

When `POST /v1/numbers` answers `202 Accepted` (an iMessage number still provisioning), the response now carries `Location: https://api.getdial.ai/v1/numbers/{id}`, the URL to poll for the number's status.

## October 29, 2026

## Changed

### WhatsApp groups count as one recipient for sending limits

A WhatsApp group is now treated like a single contact by the [WhatsApp sending limits](/documentation/reference/usage-and-limits#group-conversations): the per-recipient cap counts what your number sent into the group, and the unanswered limit counts your messages since anyone in the group last wrote. A message from any member counts as a reply. Past either, a send into the group is rejected with `429` (`rate_limited`). It applies to every group your number is in, whether it created the group or was added to it.

## October 28, 2026

## Changed

### Name the channel when you create a group

Create a group now takes a `channel` field (`--channel` in the CLI), so a request says which channel the group is created on. Only `whatsapp` is supported today, and it's the default, so requests that leave it out keep working. Any other value is rejected with `400`. [Read more](/documentation/capabilities/group-conversations#create-a-group).

## October 25, 2026

## Added

### Webhooks can cover chosen phone numbers

A webhook can now be limited to specific numbers: pass `phoneNumberIds` when you create it, and it receives events only for those numbers. Omit it and the webhook still covers every number on the account. A new `PATCH /api/v1/webhooks/{id}` endpoint lets you change a webhook's URL, event types, or numbers without recreating it, and the signing secret stays the same. [Read more](/documentation/platform/webhooks#send-different-numbers-to-different-endpoints).

## Changed

### One failing channel no longer blocks a display-profile update

Updating a number's iMessage and WhatsApp profiles together — `channel: "both"`, or per-channel fields for both channels in one request — now applies each channel on its own. A name or photo one channel rejects, or a WhatsApp line that isn't connected, fails only that channel: the other profile and the rest of the request are still saved, and the response is `200` with a new `profileResults` field giving each targeted channel's outcome — `updated`, or `failed` with the reason. Before, the whole request was rejected and nothing changed. A request is rejected only when every channel it targeted fails, and that error response now lists each channel's outcome in `profileResults` too. `dial number set` prints a warning for the failed channel and exits `0`. [Read more](/documentation/platform/manage-phone-numbers#set-one-display-identity-across-channels).

## October 20, 2026

## Added

### Allow lists for inbound calls

Decline inbound calls from anyone who isn't on a list you keep. A list covers every number on the account or just the numbers you pick, and a number's own list replaces the account-wide one. Manage lists under **Policies** in the dashboard, with `/api/v1/policies`, the `dial policy` commands, the MCP tools, or the SDKs. A declined call is recorded with the new `terminationType` `blocked` and a `blockedByPolicyId`, and emits `call.status_changed` and `call.ended` like any other call. Phone numbers now report the list screening them as `inboundCallPolicy`. [Read more](/documentation/platform/allow-lists).

## October 18, 2026

## Added

### Add a number to an existing 10DLC campaign

Business accounts can now put another number under a 10DLC campaign they already registered — no form and no fee. `GET /api/v1/10dlc/brands` lists each brand's `campaigns`, and `POST /api/v1/numbers/{id}/10dlc` accepts `{ "campaignId": "…" }`. On an approved campaign the number is ready within moments; on one still in review it's approved together with the campaign. [Read more](/documentation/platform/manage-phone-numbers#add-a-number-to-an-existing-campaign).

## October 13, 2026

## Added

### A `paused` WhatsApp status

When Dial temporarily pauses WhatsApp sending on a line, its `whatsapp.status` now reports **`paused`** instead of staying `ready`, and you get a [`number.status_changed`](/api-reference/events/number-status-changed) event for it. When sending resumes, the status returns to `ready` with a second event. Previously the line kept reporting `ready` while sends failed with `503 whatsapp_unavailable`, and nothing told you when they would start working again.

A paused line still receives WhatsApp, so `whatsapp` stays in `capabilities`; its number folds to `degraded` until the pause ends, and `error` carries a short reason you can show. A pause applies per line, so you receive one event per affected number. If you branch on `whatsapp.status`, treat `paused` as "don't send on WhatsApp until the next event moves it back to `ready`" rather than as a failure. See [WhatsApp](/documentation/capabilities/whatsapp).

## October 11, 2026

## Added

### Inbound contact cards on iMessage and WhatsApp

A contact card someone shares with your iMessage or WhatsApp line now arrives as a regular `media` item on the [`message.received`](/api-reference/events/message-received) event and on `GET /api/v1/messages`, with `contentType: "text/vcard"` and a `url` that serves the `.vcf` file itself. Previously an iMessage contact card was dropped and the message arrived empty. See [Receive an SMS](/documentation/capabilities/receive-an-sms) and [WhatsApp](/documentation/capabilities/whatsapp).

### iMessage polls

Send a native poll from an iMessage number with `poll: { title, options }` on [Send a message](/api-reference/rest-api/messages/send-message) — one-to-one or into an iMessage group — and receive every vote as a [`message.received`](/api-reference/events/message-received) event carrying `pollVote`. The poll message's new `poll.options[].votes` is the live tally. CLI: `dial message --poll "<title>" --option A --option B`. See [Send a poll](/documentation/capabilities/send-an-sms#send-a-poll-imessage-numbers).

## October 8, 2026

## Changed

### WhatsApp typing stops at about 30 seconds

The "typing…" a WhatsApp text message or text reply shows before it arrives is now capped at about 30 seconds. It still lasts about 200-250 ms per character, so a short message is unchanged, but a long message no longer keeps the send waiting for minutes. The message itself is sent in full, however long it is. See [WhatsApp](/documentation/capabilities/whatsapp).

## October 6, 2026

## Added

### `channel: "sms"` on Send a message and typing

[Send a message](/api-reference/rest-api/messages/send-message) and [Set a typing indicator](/api-reference/rest-api/messages/send-typing) accept `channel: "sms"` to name the SMS rail explicitly. On a standard number it is the same as omitting `channel`, so existing requests need no change. A number with a monthly SMS sending allowance answers `429` once that allowance is spent.

## September 30, 2026

## Changed

### WhatsApp messages type before they're sent

A WhatsApp text message, and a WhatsApp text reply, now shows "typing…" to the recipient for about 200-250 ms per character before it arrives. The request returns once the message is sent, so a WhatsApp send takes that much longer to answer: about 7 seconds for a 30-character message. Sends with an attachment, reactions, and other channels are unchanged. See [WhatsApp](/documentation/capabilities/whatsapp).

## September 28, 2026

## Added

### Calls say why they failed

A call now carries `failureReason` when it ends as `failed` and Dial knows the cause. The same value is on the [`call.ended`](/api-reference/events/call-ended) and [`call.status_changed`](/api-reference/events/call-status-changed) events. Today the reasons come from a Self-Hosted audio target: `self_hosted_key_rejected` (Pipecat Cloud rejected your public key), `self_hosted_agent_not_found`, `self_hosted_at_capacity`, and `self_hosted_unreachable`. It is `null` on every other call. More values may be added, so treat an unknown value like `null`. See [When a call fails](/documentation/platform/self-hosted#when-a-call-fails).

## September 27, 2026

## Added

### Self-Hosted audio: connect to a Pipecat Cloud agent

The audio config on `POST /api/v1/self-hosted` now accepts `pipecatCloud: { agentName, publicKey }` in place of `wsUrl`. Before every connection, including a reconnect mid-call, Dial fetches a single-use token from the agent's `/start` endpoint, so Pipecat's WebSocket token auth can stay on. See [Pipecat Cloud](/documentation/platform/self-hosted#pipecat-cloud).

`GET /api/v1/self-hosted` returns the target as `audio.pipecatCloud: { agentName, publicKeyMasked }`. For a Pipecat Cloud target `audio.wsUrl` is `null`, and for a URL target `audio.pipecatCloud` is `null`. Existing `wsUrl` configs are unchanged.

## Changed

### New WhatsApp numbers arrive warmed

Provisioning a number with `whatsapp: true` now hands you a number whose WhatsApp is already set up and warmed — `whatsapp.status` is `ready` in the response itself, with no wait. `areaCode` is not honored for these numbers. When no WhatsApp number is available right now, the request is refused with `503` `whatsapp_unavailable`; nothing was bought or charged, so retrying shortly is safe.

### Connecting WhatsApp to your own number includes a warm-up

`POST /api/v1/numbers/{id}/whatsapp` now warms the number up before its first use. After registration the WhatsApp track reports the new status **`warming_up`** for about 6 hours, then `ready`.

`warming_up` counts as still in flight: the number stays `unsettled` on [`number.status_changed`](/api-reference/events/number-status-changed), and `whatsapp` joins `capabilities` only at `ready`. If you branch on `whatsapp.status`, treat an unknown value as not ready. Wait for the event with a long timeout rather than polling tightly.

## September 23, 2026

## Added

### Groups report their channel

Each entry from `GET /api/v1/groups` now carries **`channel`** — `whatsapp` or `imessage`, the same value its messages carry — so you can tell which channel's rules apply to a group before you send into it.

```json
{ "id": "grp_123", "channel": "whatsapp", "name": "Planning bday party", "createdAt": "2026-09-01T10:22:04.000Z" }
```

`dial group list` shows it as a column. This is an added field on an existing object, so nothing that reads groups today is affected.

## Changed

### Groups have their own section in the API reference

List groups moved from **Messages** to a new **Groups** section. The endpoint is unchanged; its old reference URL redirects.

## September 22, 2026

## Changed

### A WhatsApp line in a cooling-off period now retries itself

When WhatsApp puts a number in a cooling-off period before it will accept another verification request, setup fails and `whatsapp.retryAvailableAt` says when the period lifts. Until now that was where it stopped: the line sat failed until somebody asked for a retry, and a period that expired overnight was noticed the next morning.

Dial now waits the period out and re-runs setup by itself, exactly as if the retry had been asked for. It does this a bounded number of times and then stops, so a number WhatsApp keeps refusing does not retry forever.

The `whatsapp` object on a phone number gains **`autoRetryScheduled`** to tell the two apart:

```jsonc
{
  "whatsapp": {
    "status": "failed",
    "error": "WhatsApp stopped accepting verification requests for this number. Try again in a couple of hours.",
    "retryAvailableAt": "2026-09-22T03:18:19.584Z",
    "autoRetryScheduled": true    // nothing to do — Dial will retry when the period lifts
  }
}
```

While `autoRetryScheduled` is `true`, leave the line alone and tell the user a retry is coming. When it is `false` and `retryAvailableAt` is `null`, the next attempt is yours to ask for. Asking for one yourself is unchanged, and still gives the line a fresh budget of automatic attempts.

This is an added field on an existing object, so nothing that reads a number today is affected.

## September 7, 2026

## Changed

### WhatsApp can now send media

`POST /api/v1/messages` on a WhatsApp line (`channel: "whatsapp"`) now accepts a **single** media attachment — the same `media` / `mediaUrls` fields as every other channel — where before it was text-only. More than one attachment is rejected with `400`.

A `body` may accompany media **only** when the attachment is an image or video; a body alongside audio or a document is rejected with `400`. A lone audio attachment is delivered as a **voice note** by default, and `forceAudioFile: true` delivers it as a regular audio file instead — mirroring how `forceAudioFile` already behaves on iMessage numbers. Supported attachment types are unchanged (images, audio, video, PDF, vCard, iCalendar); a vCard is delivered as a document, not a native contact card.

Nothing about the request shape changes — the fields already existed — so existing text-only WhatsApp sends are unaffected.

### WhatsApp: reply with a text body, and reply or react to group messages

`POST /api/v1/messages/{messageId}/reply` now supports a text `body` on WhatsApp numbers — delivered as a **native quoted reply** (the target message is quoted above your reply), matching how a reply looks when sent from the app. Previously only a `reaction` was supported on WhatsApp targets.

WhatsApp targets may now also be **group** messages: replying or reacting to a group message delivers the reply or reaction to that group. On other channels a group message still cannot be the target.

The request shape is unchanged — the same `body` / `reaction` fields — so existing replies are unaffected.

## September 4, 2026

## Fixed

### Replying to a message that arrived over SMS answers `400`, not `500`

A threaded reply attaches to its target by the message id the target's channel carries, and only iMessage and RCS carry one. When the other party has neither, an inbound message reaches your iMessage line over the **SMS fallback** — and a `body` reply to that message had nothing to attach to. [Reply to a message](/api-reference/messages/reply-to-message) reported that as `500 Internal server error`, which read as a fault on our side for a request that simply isn't supported.

It now answers `400` with a message naming the reason, so it's distinguishable from a real failure and safe not to retry. Nothing was ever delivered on the `500` and nothing is delivered on the `400` either — the behaviour is unchanged, only the classification.

To reach a contact whose messages arrive over SMS, send a new message ([`POST /api/v1/messages`](/api-reference/messages/send-a-message)) rather than a reply. It lands in the same conversation; it just isn't quoted on the recipient's device, which SMS can't render anyway.

Unaffected: a **`reaction`** on such a target still follows the reaction rules (an emoji is delivered as a regular message carrying the emoji; a reaction name answers `400`), and a `body` reply whose conversation merely can't thread at send time is still delivered as a regular message.

## September 3, 2026

## Added

### WhatsApp lines (beta)

WhatsApp is now a channel you can connect to a Dial iMessage number, documented at [WhatsApp lines](/documentation/capabilities/whatsapp). It's **enabled per account** while in beta — [talk to us](/documentation/reference/support) to request access, and until it's granted the WhatsApp provisioning endpoints answer `404` rather than `403`, because to an account without access the capability doesn't exist.

Buy a line with it (`whatsapp: true` on `POST /api/v1/numbers`, valid only alongside `capabilities: ["sms","call","imessage"]`) or add it to a line you already hold (`POST /api/v1/numbers/{id}/whatsapp`). A number reports the channel two ways: `capabilities` gains `whatsapp` once it works, and a `whatsapp` object carries its own `status`, `error` and `retryAvailableAt` — a track independent of `setupStatus`, since caller-ID can be ready while WhatsApp has failed.

Because a WhatsApp line carries **two rails on one number**, `channel` (`imessage` | `whatsapp`) is now accepted on `POST /api/v1/messages` and `POST /api/v1/typing`. **Omitting it keeps each number's existing behaviour**, so nothing you already send changes.

WhatsApp sends are text-only (inbound attachments are unaffected), start at `deliveryState: "unconfirmed"` and firm up as receipts arrive, report **reads** unlike SMS, and go out one at a time per line — a second concurrent send is refused rather than queued.

### Group conversations

A group is a conversation that isn't a phone number, so Dial gives it an ID of its own — see [Group conversations](/documentation/capabilities/groups).

`GET /api/v1/groups` lists the groups your lines are in. Send into one with `groupId` on `POST /api/v1/messages` (exclusive with `to`; the sending line comes from the group), and read one back with the new `GET /api/v1/messages?groupId=…` filter. Inbound group messages arrive on [`message.received`](/api-reference/events/message-received) carrying `groupId`, so `filters: { "groupId": "…" }` waits for one group's traffic.

> **Warning**
>
> **`to` is `null` on every group message**, in both directions: the destination is the group, and a group is not a phone number. Which of your numbers the conversation is on is `phoneNumberId`, which is set on every message Dial has ever recorded. If your integration reads `to` as "my number", that's the line to change.

A group's `name` may be `null` — Dial stores no copy of a subject participants rename at will, so it's read live from the line, and a line that can't answer in time yields a null name rather than failing the listing.

## Changed

`Message` now documents `groupId`, a nullable `to`, and `whatsapp` on `channel` — plus `deliveryState`, `readState`, `readAt` and `deliveryError`, which the API has returned for a while and the reference had never listed.

The Send a message reference no longer claims the channel can't be chosen per request. That was true until one line could carry both iMessage and WhatsApp, which is the case `channel` exists for.

The [MCP](/integrations/tools/mcp) tool matrix gains `list_groups` — and `start_typing` / `stop_typing`, which both servers have offered since typing shipped and the table had never listed.

## Not yet

Some of this is published ahead of the CLI, SDK and MCP releases that expose it; the REST contract above is live now, and those surfaces follow shortly.

* **Typing indicators inside a group** aren't supported — `channel: "whatsapp"` typing works one-to-one only.
* **iMessage groups** aren't supported. The group ID is channel-neutral, so they'll slot into this same shape.
* **There's no join event.** A group your line was just added to shows up as a new entry from `GET /api/v1/groups`; list groups when you need to know what's there.

## August 20, 2026

## Added

### Know when a message is delivered, rejected, or read

Sending a message told you Dial accepted it, and nothing more — the carrier's actual verdict arrived later with no way to hear about it. A new [`message.status_changed`](/api-reference/events/message-status-changed) event now publishes each advance on the account stream, so you can react the moment a message lands or is rejected instead of polling `GET /api/v1/messages`.

Delivery and reads are reported as **two independent axes**, because not every rail reports both. `deliveryState` covers `pending` → `delivered` | `undelivered` | `failed`, plus `unconfirmed` for rails that send no delivery receipts. `readState` covers `unread` → `read`, plus `unsupported` for rails that never report reads. Each event names which axis moved in `changed` and carries the current value of both, so one event is always a complete picture. Failures carry a plain-language `deliveryError`.

Every field is flat, so the stream is directly filterable — `dial wait-for message.status_changed -f deliveryState=failed` waits for a rejection. The event is also subscribable as a [webhook](/documentation/platform/webhooks).

`GET /api/v1/messages` gains the matching `deliveryState`, `readState`, `readAt`, and `deliveryError` fields, so a fetch and a stream event never disagree.

## Changed

### `status` is delivery-only; reads moved to `readState`

A message's `status` no longer returns `read`. Reads now live on the independent `readState` and `readAt` fields, which means a message can be reported as both delivered **and** read — previously one value had to stand for both, so a read receipt hid the delivery confirmation. A read message now reports `status: "delivered"` with `readState: "read"`.

`status` and `statusError` keep working and still describe delivery only. `statusError` is identical to the new `deliveryError`. `status` is the same axis as `deliveryState` in the older vocabulary, differing on two values: `sent` is `pending`, and `unknown` is `unconfirmed`. If you branch on `status === "read"`, move to `readState === "read"`.

## August 14, 2026

## Changed

### Self-Hosted mode is approved per account

[Self-Hosted mode](/documentation/platform/self-hosted) hands live calls to a server Dial doesn't run, so new accounts now request access before they can point calls at one. **Accounts already using Self-Hosted are unaffected** — they keep it, and nothing about their configuration changes.

`GET /api/v1/self-hosted` gains an `access` field (`none`, `pending`, `granted`, `denied`) and an `accessRequest` object carrying the request you submitted and how it was reviewed. A new `POST /api/v1/self-hosted` action, `request_access`, submits one — a `useCase` describing what you're building, and an optional `companyUrl`. A human reviews it.

Until access is granted, `save` and `activate` return **403**. `disable` and `request_access` are never gated, so an account can always turn Self-Hosted off.

_Showing the 20 most recent of 33 entries. Append `/llms.txt` to the changelog URL for the complete index._