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

# Manage phone numbers

> List and purchase the phone numbers on your Dial account.

Every message and call is sent *from* a Dial phone number. Your first number is included with onboarding; you can list your numbers and purchase more at any time.

## List your numbers

Each number has an `id` (used as `fromNumberId`), the `number` itself in E.164, its `country`, its `capabilities`, an optional `nickname` (a human-readable label you choose), and its `inboundInstruction` (the system prompt its AI voice agent uses on inbound calls).

```bash title="CLI"
dial number list --json
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const numbers = await dial.listNumbers();
for (const n of numbers) console.log(n.id, n.number, n.country);
```

```python title="Python"
from dial_sdk import DialClient, DialConfig

dial = DialClient(DialConfig(api_key="sk_live_..."))
for n in await dial.list_numbers():
    print(n.id, n.number, n.country)
```

```bash title="cURL"
curl https://api.getdial.ai/v1/numbers \
  -H "Authorization: Bearer sk_live_..."
```

## Purchase a number

Request a new number, optionally in a specific area code. Only US numbers can be provisioned at this time. An `inboundInstruction` is **optional** — it becomes the new number's inbound voice-agent prompt, and a default greeting is used if you omit it.

Every purchase requires `explicitProgrammaticConsent` (CLI: `--explicit-programmatic-consent`): a short, human-readable attestation that the account holder has explicitly consented to provisioning this number programmatically. It's stored on the number for the provisioning audit trail. Omitting it is rejected with `400`.

```bash title="CLI"
dial number purchase \
  --inbound-instruction "You are my receptionist. Greet the caller and find out what they need." \
  --explicit-programmatic-consent "Account holder approved via our signup flow" \
  --area-code 415 --json
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const number = await dial.purchaseNumber({
  inboundInstruction: "You are my receptionist. Greet the caller and find out what they need.",
  explicitProgrammaticConsent: "Account holder approved via our signup flow",
  areaCode: "415",
});
console.log(number.id);
```

```python title="Python"
from dial_sdk import DialClient, DialConfig, PurchaseNumberParams

dial = DialClient(DialConfig(api_key="sk_live_..."))
number = await dial.purchase_number(PurchaseNumberParams(
    inbound_instruction="You are my receptionist. Greet the caller and find out what they need.",
    explicit_programmatic_consent="Account holder approved via our signup flow",
    area_code="415",
))
print(number.id)
```

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/numbers \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"inboundInstruction":"You are my receptionist. Greet the caller and find out what they need.","explicitProgrammaticConsent":"Account holder approved via our signup flow","areaCode":"415"}'
```

The purchased number is returned with its `id`. Pass that `id` as `fromNumberId` when you [send a message](/documentation/capabilities/send-an-sms) or [place a call](/documentation/capabilities/place-a-voice-call).

### Purchase an iMessage number

Add `--include-imessage` (CLI), `includeImessage: true` (Node), or `include_imessage=True` (Python) — or include `imessage` in `capabilities` over REST — to provision an [iMessage number](/documentation/capabilities/send-an-imessage). Two things differ from a standard number:

* **Pay-as-you-go only.** iMessage numbers can't be added to a flat-rate subscription; an account on a subscription is rejected with `403`. Switch to pay-as-you-go to add one.
* **Messaging is free.** Every message from an iMessage number — the iMessage itself, and any automatic RCS/SMS fallback — is billed at **\$0** (it appears in your usage as the `imessage.message` fare). You pay only the number's monthly ownership; the per-message SMS tiers don't apply. Calls are billed at the usual per-minute rate.
* **Asynchronous setup.** The number is returned immediately with `setupStatus: provisioning`. Poll [List your numbers](#list-your-numbers) until its `setupStatus` is `ready` before you send or call from it. (`areaCode` is ignored for iMessage numbers.)

```bash title="CLI"
dial number purchase \
  --include-imessage \
  --inbound-instruction "You are my receptionist. Greet the caller and find out what they need." \
  --explicit-programmatic-consent "Account holder approved via our signup flow" \
  --json
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const number = await dial.purchaseNumber({
  includeImessage: true,
  inboundInstruction: "You are my receptionist. Greet the caller and find out what they need.",
  explicitProgrammaticConsent: "Account holder approved via our signup flow",
});
console.log(number.id, number.setupStatus); // "provisioning" — poll until "ready"
```

```python title="Python"
from dial_sdk import DialClient, DialConfig, PurchaseNumberParams

dial = DialClient(DialConfig(api_key="sk_live_..."))
number = await dial.purchase_number(PurchaseNumberParams(
    include_imessage=True,
    inbound_instruction="You are my receptionist. Greet the caller and find out what they need.",
    explicit_programmatic_consent="Account holder approved via our signup flow",
))
print(number.id, number.setup_status)  # "provisioning" — poll until "ready"
```

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/numbers \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"capabilities":["sms","call","imessage"],"inboundInstruction":"You are my receptionist. Greet the caller and find out what they need.","explicitProgrammaticConsent":"Account holder approved via our signup flow"}'
```

## Set the inbound instruction

Change how a number's AI voice agent behaves on inbound calls at any time. The change takes effect on the next inbound call; calls in progress are unaffected.

```bash title="CLI"
dial number set +14155550123 \
  --inbound-instruction "You are ACME's receptionist. Greet the caller and route them."
```

```bash title="cURL"
curl -X PATCH https://api.getdial.ai/v1/numbers/pn_123 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"inboundInstruction":"You are ACME'\''s receptionist. Greet the caller and route them."}'
```

The CLI takes the number in E.164 and resolves it to its `id` for you; the REST API takes the number `id` in the path.

## Set the inbound language

By default the agent detects the language from the caller's country prefix on each inbound call, and handles both that language and `en-US`. Set the number's `inboundLanguage` — a BCP-47 tag such as `es-ES` — to pin every inbound call to a single language instead. Clear it (empty string in the CLI, `null` over REST) to go back to per-call detection. Takes effect on the next inbound call.

```bash title="CLI"
dial number set +14155550123 --inbound-language es-ES
```

```bash title="cURL"
curl -X PATCH https://api.getdial.ai/v1/numbers/pn_123 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"inboundLanguage":"es-ES"}'
```

You can also set it at purchase time with `--inbound-language` (REST: `inboundLanguage`).

## Give a number a nickname

Attach a human-readable label to a number — useful once an account holds several (e.g. "Support line", "Sales — EU"). Nicknames are free text up to 100 characters and don't have to be unique. Clear one by setting it to an empty string (CLI) or `null` (REST).

```bash title="CLI"
dial number set +14155550123 --nickname "Support line"
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const number = await dial.setNumberProperties("pn_123", { nickname: "Support line" });
console.log(number.nickname);
```

```python title="Python"
from dial_sdk import DialClient, DialConfig

dial = DialClient(DialConfig(api_key="sk_live_..."))
number = await dial.set_number_properties("pn_123", nickname="Support line")
print(number.nickname)
```

```bash title="cURL"
curl -X PATCH https://api.getdial.ai/v1/numbers/pn_123 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"nickname":"Support line"}'
```

`nickname`, `inboundInstruction`, and `inboundLanguage` all go through the same update call — send any subset of them in one request.

## Set the iMessage display identity

Numbers with the `imessage` capability can carry a **display identity** — a first name, last name, and avatar photo shown beside the number's messages in recipients' Messages apps. Names are up to 30 characters each; clear one by setting it to an empty string (CLI) or `null` (REST). The photo accepts jpeg, png, gif, or webp up to 5 MB — a square image of 512×512 or larger is recommended (it's shown as a circle). Photos can be **replaced but not removed**. Setting any identity field on a number without the `imessage` capability is rejected with `400`.

Upload the photo as a `multipart/form-data` file part, or pass a public image URL as `avatarUrl` (JSON or multipart) for Dial to download server-side. Either way, Dial mirrors the photo into its own storage and returns its stable public URL as the number's `avatarUrl`. Identity changes can take a few minutes to propagate to recipients' devices.

```bash title="CLI"
dial number set +14155550123 --first-name Maya --last-name Chen --avatar ./avatar.png
```

```typescript title="Node"
import { DialClient } from "@getdial/sdk";

const dial = new DialClient({ apiKey: process.env.DIAL_API_KEY! });
const number = await dial.setNumberProperties("pn_123", {
  firstName: "Maya",
  lastName: "Chen",
  avatar: { path: "./avatar.png" },
});
console.log(number.avatarUrl);
```

```python title="Python"
from dial_sdk import DialClient, DialConfig

dial = DialClient(DialConfig(api_key="sk_live_..."))
number = await dial.set_number_properties(
    "pn_123", first_name="Maya", last_name="Chen", avatar="./avatar.png"
)
print(number.avatar_url)
```

```bash title="cURL (multipart upload)"
curl -X PATCH https://api.getdial.ai/v1/numbers/pn_123 \
  -H "Authorization: Bearer sk_live_..." \
  -F firstName=Maya \
  -F lastName=Chen \
  -F avatar=@avatar.png
```

```bash title="cURL (JSON, avatar by URL)"
curl -X PATCH https://api.getdial.ai/v1/numbers/pn_123 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Maya","avatarUrl":"https://your-cdn.example.com/avatar.png"}'
```

## Register a US number for 10DLC

US carriers **block** outbound SMS and MMS sent from an unregistered 10-digit number to a US phone number. It isn't filtering or a delay — the message doesn't arrive. Registering the number for 10DLC lifts the block.

What the block does **not** affect: inbound texts to your number, voice calls in either direction, and messaging to numbers outside the US. Those work whether or not the number is registered.

### Which numbers this applies to

A number is eligible when it's US, has the `sms` capability, and does **not** have the `imessage` capability — iMessage numbers send over Apple Messages for Business rather than carrier SMS, so 10DLC never applies to them. Registration is currently offered on **pay-as-you-go** accounts.

Read `tenDlc` on the number to know where you stand. It's `null` when 10DLC doesn't apply, and otherwise carries a `status`:

| `status`         | Meaning                                                                       |
| ---------------- | ----------------------------------------------------------------------------- |
| `not_registered` | Nothing submitted yet — outbound US SMS/MMS from this number is blocked.      |
| `in_review`      | Submitted; Dial is checking the details.                                      |
| `with_carrier`   | Filed with the carrier registry. Decisions usually land in 3–5 business days. |
| `approved`       | Registered — the number can send to US numbers.                               |
| `rejected`       | Changes needed. `reason` says exactly what; fix it and resubmit for free.     |

### What it costs

**\$25.00 one-time per number**, taken from your credit balance when the submission is accepted. A balance below the fee is rejected with `402`. **Resubmitting after a rejection is free** — a number is charged at most once, ever.

### Submitting

A registration has two halves, mirroring what the carrier registry registers: a **brand** — who is registering, the identity carriers vet — and a **campaign** — what you send and how people agreed to receive it. Your number is registered by joining the campaign.

Register as a **sole proprietor** if you operate under your own name and have no EIN: fewer fields, lower throughput, quicker approval. Register as a **business** if you're a registered company with an EIN. The full field list for each is in the API reference, under `POST /api/v1/numbers/{id}/10dlc`.

Carriers require a privacy policy and a terms page on every campaign, and they read them. A business gives its own `privacyPolicyUrl` and `termsUrl`, and reviewers expect to find them on the `websiteUrl` you registered — a policy somewhere unrelated to the brand is a rejection. A sole proprietor usually has no website, so both are optional: leave them out, send `"acceptPublishedNotice": true`, and Dial publishes a privacy notice and a terms page for the brand — written from what you submitted, naming you as the operator of the messaging program — and registers those URLs. Read them back from the registration and link them wherever people opt in. Your own URLs, if you send them, always win.

One field is easy to get wrong: a sole proprietor gives a `vertical` — the trade they operate in — while a business gives a `businessIndustry`. They look interchangeable and aren't. The registry validates them against different vocabularies, so `PROFESSIONAL` is a vertical while `PROFESSIONAL_SERVICES` is an industry, and several industries have no vertical at all. Take the value from the list for the field you're actually sending.

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/numbers/pn_123/10dlc \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "business",
    "brand": {
      "brandName": "Northwind Supply Co.",
      "firstName": "Maya", "lastName": "Chen",
      "email": "maya@northwind.io", "phone": "+14155550110",
      "street": "480 Bryant St", "city": "San Francisco", "country": "US", "state": "CA", "zip": "94107",
      "businessType": "Limited Liability Corporation",
      "businessIndustry": "RETAIL",
      "registrationIdType": "EIN", "registrationNumber": "84-3921776",
      "websiteUrl": "https://northwind.io",
      "jobPosition": "Other", "businessTitle": "Head of Operations",
      "businessRegionsOfOperation": "USA_AND_CANADA", "companyType": "private"
    },
    "campaign": {
      "useCase": "CUSTOMER_CARE",
      "description": "We text customers who placed an order to confirm delivery windows and let them reschedule. Every recipient checked the SMS box at checkout.",
      "messageFlow": "Customers tick an unchecked SMS consent box at checkout under the heading Text me delivery updates, beside message frequency and STOP instructions.",
      "samples": [
        "Northwind Supply: your order #40218 is out for delivery today between 2-4pm. Reply STOP to opt out.",
        "Northwind Supply: thanks for your order! Track it at northwind.io/track. Reply STOP to unsubscribe."
      ],
      "privacyPolicyUrl": "https://northwind.io/privacy",
      "termsUrl": "https://northwind.io/terms",
      "containsUrls": true, "containsPhones": false
    }
  }'
```

Then read the registration back — brand, campaign, and where it stands — with `GET /api/v1/numbers/pn_123/10dlc`.

The number you register is always a US number — that's what 10DLC is — but **your business doesn't have to be US-based**. A business may be registered from any country the registry accepts, using its own country's tax ID; give the `country` of your registered address and its `state` — the two-letter code in the US and Canada, the region name elsewhere. Sole proprietor registration is the exception: it's available in the **US and Canada only**.

The brand's `phone` may not be one of your Dial numbers. Carriers won't verify a number issued by a messaging provider, so use a mobile the person named on the registration answers directly. For a sole proprietor that number receives a verification text — reply `YES` within 24 hours or the registration is turned down.

Carriers reject vague answers. Say concretely who is messaged, why, and how they agreed to it; keep marketing language out of it. Sample messages must name your brand and end with an opt-out instruction — at least two are required, and repeating one is fine if that's the only message you'll ever send. If you write your own privacy policy, it has to state that mobile information and messaging consent are never shared or sold to third parties or affiliates for marketing — the single most common reason a campaign is turned down. If your registration is rejected, the `reason` tells you precisely what to change — a resubmission costs nothing.

Dial emails you when the registration is approved or rejected. Once approved, keep your message content matching what you described — carriers audit it, and violations can suspend the number.

## Release a number, and what it costs

Releasing a number returns it to the carrier and removes it from your account, immediately and irreversibly. A released number can't be reclaimed.

On **pay-as-you-go** there's nothing more to it — you simply stop being charged the monthly ownership fee for it.

On a **flat-rate subscription**, a release lowers the subscription by one number and Stripe prorates the change. The quantity never drops below **one number**, though, which means:

* Releasing your **last** number leaves the subscription active at the single-number rate — it isn't cancelled. Cancel the subscription itself (`POST /api/v1/billing/subscription/cancel`) to stop paying.
* While you hold no numbers, the **next number you provision is free** — the subscription is already paying for one, so there's no prorated charge. Holding zero numbers and holding one cost exactly the same.
* The new number is covered by the subscription as soon as the purchase returns, so you can send and call from it right away.

Swapping a number — release, then provision a replacement — is therefore free on a subscription, however many times you do it.

iMessage numbers are pay-as-you-go only and are never part of a flat-rate subscription, so none of the quantity rules above apply to them.

## Non-payment and number release

Each number carries a small monthly ownership fee. On pay-as-you-go, that fee is drawn from your account balance, which is allowed to go negative — so a lapsed top-up doesn't interrupt service immediately.

You have a **30-day grace period**, measured from the moment your balance first goes negative. During grace, ownership fees keep being charged and your numbers keep working. Topping your balance back to zero or above resets the grace period.

If your balance is still negative when the grace period ends, Dial stops charging for ownership and instead **releases your numbers** — returning them to the carrier and removing them from your account. Once the grace period ends, all of your numbers are released together (Dial checks daily), so add credit before the grace period ends to keep them. A released number is gone and cannot be reclaimed; purchase a new one to continue.

Numbers covered by an active subscription are unaffected by your balance — they are never released for non-payment.