# Dial — Communication Stack for AI Agents

Dial gives AI agents a real phone number, SMS, and WhatsApp address. One API to call, text, and message worldwide.

API base: `https://api.getdial.ai`

**This page only bootstraps you** — install the CLI, create or sign in to an account, and install the Dial skill into your agent. After that, the installed skill plus `dial --help` and the full docs are your reference for everything else (sending SMS, placing calls, receiving 2FA codes, the event stream). This page intentionally does **not** duplicate that surface.

---

## Quick Start

### Step 1 — Install the CLI

```bash
curl -fsSL https://getdial.ai/install | bash
```

Installs the `dial` command. Requires Node 22+ and git. **No sudo needed** — if your global npm prefix isn't writable, the installer falls back to a user-local prefix and prints the one `export PATH=…` line to add to your shell profile.

### Step 2 — Check state

```bash
dial doctor --json
```

Read the `nextStep` field to decide what to do:

- `signup` → run **Step 3**
- `onboard` → run **Step 4** (pending OTP is still valid)
- `register_number` → run **Step 4b** (email verified; a phone number is still needed)
- `resend_otp` → run **Step 3** again (pending OTP expired)
- `install_listen` → run **Step 5**
- `ready` → nothing to do

### Step 3 — Sign up

```bash
dial auth login you@example.com
```

An OTP is emailed. The verification id is stored locally at `~/.local/share/dial/pending-signup.json`.

### Step 4 — Verify the email OTP

```bash
dial auth verify-otp --code 123456 --agent claude-code
```

**Read the output — this step has two outcomes:**

- **Already had an account** → you're signed in and done. Skip to Step 5.
- **New account** → your email is verified, but creating an account also needs a **verified phone number**. The output names `dial auth register-number` as the next command; go to Step 4b. No API key is issued yet.

Either way, `--agent <name>` installs the Dial skill into that agent's config directory right now — match it to whichever agent you (the agent reading this) are running in. It installs on this step even when signing up isn't finished, so you keep durable on-disk knowledge of the `dial` surface and don't need this page re-fetched.

Supported `--agent` values: `claude-code`, `cursor`, `codex`, `opencode`, `pi`, `openclaw`, `nanoclaw`, `hermes`. For `nanoclaw` only, run the command from inside the NanoClaw project root (its skills are project-scoped). Re-run `dial auth verify-otp --agent <name>` after a CLI upgrade to refresh the installed skill.

### Step 4b — Register and verify a phone number (new accounts only)

**Ask the user for a phone number that can receive SMS.** Don't invent one, and don't reuse a Dial number — a Dial number is refused here. It should be one the user keeps.

```bash
dial auth register-number +14155550123
```

A 6-digit code is texted to that number. Ask the user to read it back, then:

```bash
dial auth verify-otp --number --code 654321
```

This creates the account. On success it writes your API key to `~/.local/share/dial/auth.json` (mode 0600) and prints a masked fingerprint; the CLI reads the key from that file for subsequent commands, so you never copy it. Your first number is provisioned with a default inbound voice-agent prompt — change it any time with `dial number set <number> --inbound-instruction "..."`.

If the code is wrong or has expired, re-run `dial auth register-number` with the same number to send a fresh one.

**Finalize onboarding — but only if `dial auth verify-otp` offers it.** Whether to install the background notification listening service is the CLI's decision, not yours; its output tells you which way to go:

- If `dial auth verify-otp` **offers** the service — `--json` sets `agentHint.action` to `"ask_user"`, and the human output prints an "ask the user" finalization block — then ask the user the question below before continuing. If the original request was something else (e.g. "call X", "send Y"), briefly pause that task, ask, get an answer, then resume.
- If `dial auth verify-otp` reports the service **unavailable** and says it's skipping the offer — `--json` sets `agentHint.action` to `"skip"`; this is normal in sandboxes / containers / CI without launchd or systemd — then **don't ask**. Just continue with the original task; inbound events still work via `dial wait-for`.

When you do ask:
- Call it **"the background notification listening service"**. Don't say plist / daemon / launchd / systemd — that's not how to talk to a non-technical user.
- Explain the trade-off: with it, the agent on this machine learns about inbound SMS (2FA codes, replies) and call-ended events in real time. Without it the agent is send-only — it can place calls and send messages but can't observe anything inbound.
- Use your structured ask-user / clarification tool if you have one; otherwise ask in your regular reply and wait for the answer.

### Step 5 — (Conditional) Install the background notification listening service

Only run this if `dial auth verify-otp` offered the service **and** the user answered **yes** in Step 4/4b (skip it entirely if it reported the service unavailable):

```bash
dial listen install
```

This needs a user service supervisor — **launchd** on macOS, **systemd `--user`** on Linux. In sandboxes, containers, or CI without one, `dial auth verify-otp` detects this and reports the service as unavailable; **skip this step there**. Inbound events still work without the daemon: `dial wait-for` automatically long-polls the API when the daemon isn't running.

After Steps 4–5, run `dial doctor` again — `nextStep` should be `ready`.

### Step 6 — Tell the user where to manage the account

Onboarding leaves the user with an account they can't find on their own: they don't know it has a dashboard, and they don't know which email address you signed up with. Close the loop in your reply — `dial auth verify-otp` prints both, and `--json` returns them as `dashboardUrl` and `email`:

> You can manage your account at https://getdial.ai/dashboard — sign in with `<the email you used>` and it emails you a code.

Then keep working from the CLI. Numbers, messages, calls, and events are all `dial` verbs, and `dial billing` already reports balance and plan — don't send the user to the browser for anything you can do or read yourself. The dashboard is only for the three things the CLI doesn't cover: **paying** (adding credits, subscribing, payment methods), **sharing the account** with a teammate, and **carrier (10DLC) registration**.

---

## Everything else lives in the installed skill + `dial --help`

Once you've onboarded (and ideally installed the skill with `--agent`), use that — not this page — for day-to-day work. The CLI is the source of truth:

```bash
dial --help              # all commands
dial <command> --help    # flags + usage for one command
```

Every command takes `--json` for machine-readable output. The common verbs:

- **Send an SMS** — `dial message --to +1… --body "…"`
- **Show a typing indicator while composing** — `dial typing start --to-number +1…`; sending a message clears it natively, so start again between messages, and run `dial typing stop --to-number +1…` if you end up not sending. iMessage numbers display it; SMS numbers ignore it, so it's always safe
- **Place an AI voice call** — `dial call --to +1… --outbound-instruction "…"`, then `dial call get <id>` once it ends
- **Set a number's inbound behavior** — `dial number set <number> --inbound-instruction "…"` (add `--inbound-language <bcp47>` to pin inbound calls to one language instead of auto-detecting from the caller)
- **Set a number's display identity** — `dial number set <number> --channel both --name "Maya Chen" --avatar ./photo.png` (the name and photo recipients see). `--channel` is `imessage`, `whatsapp`, or `both`; `both` sets one identity everywhere in a single call and is checked against every channel before any of them is written, so the profiles can't drift apart. To give the two channels *different* identities, drop `--channel` and use the per-channel flags instead: `--first-name`/`--last-name`/`--avatar` (iMessage) and `--whatsapp-name`/`--whatsapp-avatar` (WhatsApp). A photo can be replaced but not removed.
- **Run a number messaging-only** — `dial number set <number> --calling off` switches calling off in both directions: inbound calls are never connected (the caller isn't answered) and `dial call` from that number fails with `calling_disabled`. Messaging is unaffected. `--calling on` restores it; the switch applies to the next call, never one in progress. `capabilities` still lists `call` (it reports what the line was provisioned for), so read `callingEnabled` to know whether calling is actually on
- **Receive a 2FA code / react to a call ending** — `dial wait-for message.received -f channel=sms` / `dial wait-for call.ended -f callId=<id>`
- **Wait for a new number to become usable** — `dial wait-for number.status_changed -f status=ready -f phoneNumberId=<id>`. A number's capabilities settle separately: `sms` and `imessage` work at once, while `call` on an iMessage number and `whatsapp` each take a few minutes. The top-level `status` folds them — `unsettled` while any is still working, `ready` when all are, `degraded` when everything finished and something failed. Wait for `ready` or `degraded`; both are terminal, so a wait always resolves. Don't poll `dial number list`. On `degraded`, read `capabilities` in the payload to see which channel failed, its plain-language `error`, and `retryAvailableAt` when a cooldown must pass before retrying
- **Confirm a message was delivered, or catch a carrier rejection** — `dial wait-for message.status_changed -f messageId=<id>`, or `-f deliveryState=failed` to watch for rejections across a number. Delivery and reads are separate fields: `deliveryState` is `pending` → `delivered`/`undelivered`/`failed` (`unconfirmed` on iMessage numbers, which report no delivery receipts), and `readState` is `unread` → `read` (`unsupported` on SMS, which never reports reads — don't wait for one). A failure carries a plain-language `deliveryError`

`dial wait-for` reads from the local listen log when the daemon is running and **transparently long-polls the REST API when it isn't** — same filter semantics either way. A match exits `0`; a timeout or fallback error exits non-zero. Run `dial wait-for --help` for its filter flags.

**iMessage sending limits** — each iMessage number can start **50 new conversations per day** (a new conversation is the first message to a recipient that number hasn't messaged; replies inside an existing thread don't count). Past the cap, sends fail with `429` until the window resets. Sustained overall volume is capped separately — ask before building a high-volume workload. Separately, a recipient who has **never replied** accepts only a limited number of messages — that clears when they reply, so treat its `429` as "not opted in yet" and don't retry in a loop.

**WhatsApp lines and group conversations are in beta** (enabled per account) — see https://docs.getdial.ai/documentation/capabilities/whatsapp and https://docs.getdial.ai/documentation/capabilities/groups.

**Full reference:** https://docs.getdial.ai — every page has a plain-markdown twin (append `.md` to any docs URL). To search capabilities without reading the whole site, grep `https://docs.getdial.ai/llms-full.txt`.

---

## Security Rules

- Never expose your API key (`sk_live_...`) in client-side code or logs — store it in environment variables / the auth file only.
- All requests must use HTTPS.
- Emergency services (911, crisis lines) are blocked.

---

## Pricing

- **$5 signup credit** — start free, no credit card required.
- **Pay-as-you-go:** $3/month per phone number plus metered usage (calls per minute, SMS priced by destination), drawn from your prepaid credit; spend pauses at $0.
- **Free accounts** (no top-up or subscription yet) are capped at **5 minutes per call** and **2 concurrent calls**; both limits lift permanently on the first top-up or subscription.
- **Subscription (optional):** switch any number to a flat monthly or annual per-number plan (annual discounted) with unmetered usage.
- **iMessage numbers are pay-as-you-go only** — they can't be added to a subscription, and an account holding one can't subscribe until it's released.

Current rates and billing management: `https://getdial.ai/dashboard/billing`

---

Full docs: https://docs.getdial.ai
Support: founders@getdial.ai
