> 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.

# Connect WhatsApp to a number

POST https://api.getdial.ai/api/v1/numbers/{id}/whatsapp

Connects WhatsApp to an iMessage number you already hold — the second
entry point into the same flow that `whatsapp: true` on Provision a
phone number takes.

WhatsApp is in beta and enabled **per account**. Without access this
endpoint answers `404`, not `403`: to such an account the capability
does not exist, which is also what the dashboard shows. Contact support
to request access.

Asynchronous. The response carries the number as List phone numbers
reports it, with `whatsapp.status` at `provisioning`; poll until it is
`ready` before sending on the channel. Progress and failure are
independent of the number's own `setupStatus` — see WhatsApp lines.


Reference: https://docs.getdial.ai/api-reference/rest-api/phone-numbers/add-whatsapp-to-number

## Authentication

- `Authorization` header (bearer token, required) — Your Dial API key, sent as `Authorization: Bearer sk_live_...`

## Request

### Path parameters

- `id` (string, required) — The phone number's ID.

## Response

### 200

WhatsApp setup started. The number, with its `whatsapp` track attached.

- `number` (object, optional)
  - `id` (string, optional)
  - `number` (string, optional) — The number in E.164 format.
  - `nickname` (string, optional, nullable) — Optional human-readable label for the number, e.g. "Support line". Free text, at most 100 characters, not required to be unique. Null when unset. Set and cleared with Update a phone number.
  - `firstName` (string, optional, nullable) — iMessage display first name — with `lastName` and the avatar photo, the identity shown beside this number's messages in recipients' Messages apps. At most 30 characters. Null when unset, and always null on numbers without the `imessage` capability. Set and cleared with Update a phone number.
  - `lastName` (string, optional, nullable) — iMessage display last name. At most 30 characters. Null when unset, and always null on numbers without the `imessage` capability. Set and cleared with Update a phone number.
  - `avatarUrl` (string, optional, nullable) — Public URL of the number's current iMessage avatar photo, hosted by Dial. Null when no photo has been set, and always null on numbers without the `imessage` capability. Set with Update a phone number; the photo can be replaced but not removed.
  - `country` (string, optional) — ISO-3166-1 alpha-2 country code of the number. Always `US` — only US numbers can be provisioned at this time.
  - `capabilities` (list of enum, optional) — Channels the number supports. `["sms","call"]` for a standard number; `["sms","call","imessage"]` for an iMessage number (which sends iMessage with automatic fallback to RCS/SMS); that plus `whatsapp` once a WhatsApp registration on the line is live. `whatsapp` appears only when the channel actually works — while it is still being set up the line reports the iMessage list, and `whatsapp.status` is where the progress shows. Mirrors the `capabilities` accepted when provisioning, plus `whatsapp`, which is requested with the separate `whatsapp` flag rather than in this array.
    - Allowed values: `sms`, `call`, `imessage`, `whatsapp`
  - `setupStatus` (enum, optional) — Whether the number is ready to use. An SMS/call number is always `ready`. An `imessage` number is `provisioning` while it's being set up and only becomes usable for sending or calling once `ready` (`failed` on a setup error). Every change to this field emits a `number.status_changed` event — wait for that rather than polling.
    - Allowed values: `provisioning`, `ready`, `failed`
  - `setupError` (string, optional, nullable) — Human-readable reason when `setupStatus` is `failed`; null otherwise.
  - `inboundInstruction` (string, optional, nullable) — System prompt the AI voice agent uses on inbound calls to this number. Set at provisioning time and changeable with Update a phone number. Null for numbers provisioned before this field existed — those reject inbound calls until an instruction is set.
  - `inboundVoiceGender` (enum, optional) — Voice gender the AI agent uses on inbound calls to this number. Null when unset — the voice then uses the default, female. Set and cleared with Update a phone number.
    - Allowed values: `male`, `female`
  - `inboundLanguage` (string, optional, nullable) — BCP-47 language tag the AI voice agent uses on inbound calls to this number. Null when unset — the language is then detected from the caller's country prefix on each call, with the agent handling both that language and `en-US`. Set and cleared with Update a phone number.
  - `accountId` (string, optional)
  - `createdAt` (datetime, optional)
  - `maxCallDurationSeconds` (integer, optional, nullable) — Call-length cap in seconds; the effective cap on a call is the first one set in priority order — per-call, then number, then account — falling back to the global default. A higher-priority level overrides a lower one. Null means this level sets no cap. Free accounts (never added credit, never subscribed) are additionally hard-capped at 300 seconds regardless of any configured cap.
  - `whatsapp` (object, optional, nullable) — The WhatsApp channel's own setup state, or null when the number has no WhatsApp registration at all — which is every number unless the beta was enabled and WhatsApp was requested for this line. **Independent of `setupStatus`.** They are separate tracks on one line: voice caller-ID can be `ready` while WhatsApp is `failed`, and the other way round, so one status field could not carry both. See WhatsApp lines.
    - `status` (enum, optional) — Where WhatsApp setup stands for this line. Sending with `channel: "whatsapp"` is rejected until `ready`.
      - Allowed values: `provisioning`, `ready`, `failed`
    - `error` (string, optional, nullable) — Human-readable reason when `status` is `failed`; null otherwise.
    - `retryAvailableAt` (datetime, optional, nullable) — When WhatsApp will accept another verification attempt for this number, or null. Only ever set on a `failed` track, and only while still in the future. It is the one failure that can be neither fixed nor retried around — enforced per number by WhatsApp itself — so a retry is refused until it lifts. Show the time rather than a button that cannot work.
  - `tenDlc` (object, optional, nullable) — US 10DLC carrier registration for this number. Null when 10DLC does not apply to it — a non-US number, a number with the `imessage` capability (those send over Apple Messages for Business, not carrier SMS), or a number on an account that isn't pay-as-you-go and has no registration yet. Otherwise an object whose `status` says where the registration stands, starting at `not_registered`. US carriers block outbound SMS and MMS sent from an unregistered 10-digit number to US phone numbers. Registering lifts that block for this number. Inbound texts, voice calls in either direction, and messaging to numbers outside the US are unaffected.
    - `status` (enum, required) — Where this number's registration stands. - `not_registered` — 10DLC applies to this number but nothing has been submitted. Outbound US SMS/MMS is blocked by carriers. - `in_review` — submitted; Dial is checking the details before filing them with the carrier registry. - `with_carrier` — filed with the carrier registry; a decision usually lands within 3–5 business days. - `approved` — registered. The number can send outbound SMS and MMS to US phone numbers. - `rejected` — changes are needed before it can be registered. See `reason`, then resubmit (no additional fee). The brand and the campaign are vetted separately, and this is the combined outcome: `approved` means both cleared, and `rejected` means either was turned down.
      - Allowed values: `not_registered`, `in_review`, `with_carrier`, `approved`, `rejected`
    - `kind` (enum, required) — Who the number was registered as. Null while `status` is `not_registered`.
      - Allowed values: `sole_proprietor`, `business`
    - `submittedAt` (datetime, required, nullable) — When the current submission was sent. Null while `status` is `not_registered`. A resubmission after a rejection updates this.
    - `reviewedAt` (datetime, required, nullable) — When the registration was approved or rejected. Null until then.
    - `reason` (string, required, nullable) — What needs to change, verbatim as shown to the account owner. Non-null only when `status` is `rejected`.

## Examples

**Response**

```json
{
  "number": {
    "id": "string",
    "number": "+14155550123",
    "nickname": "Support line",
    "firstName": "Maya",
    "lastName": "Chen",
    "avatarUrl": "https://getdial.ai/public-media/9f2c47a1b8d3e6f0a1b2c3d4.png",
    "country": "US",
    "capabilities": [
      "sms",
      "call"
    ],
    "setupStatus": "ready",
    "setupError": "string",
    "inboundInstruction": "You are Dial's receptionist. Greet the caller and find out what they need.",
    "inboundVoiceGender": "female",
    "inboundLanguage": "es-ES",
    "accountId": "string",
    "createdAt": "2024-01-15T09:30:00Z",
    "maxCallDurationSeconds": 1,
    "whatsapp": {
      "status": "ready",
      "error": null,
      "retryAvailableAt": null
    },
    "tenDlc": {
      "status": "in_review",
      "kind": "business",
      "submittedAt": "2024-01-15T09:30:00Z",
      "reviewedAt": "2024-01-15T09:30:00Z",
      "reason": "The message flow describes a checkout page rather than the SMS consent step. Say where the consent wording and the checkbox appear, and that the box starts unchecked."
    }
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.getdial.ai/api/v1/numbers/id/whatsapp"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.getdial.ai/api/v1/numbers/id/whatsapp';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.getdial.ai/api/v1/numbers/id/whatsapp"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.getdial.ai/api/v1/numbers/id/whatsapp")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.getdial.ai/api/v1/numbers/id/whatsapp")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.getdial.ai/api/v1/numbers/id/whatsapp', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.getdial.ai/api/v1/numbers/id/whatsapp");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.getdial.ai/api/v1/numbers/id/whatsapp")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```