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

# Phone numbers

> Provision, manage, and release SMS- and voice-enabled phone numbers

Numbers have different capabilities: standard phone numbers support SMS/MMS and voice; iMessage lines require a separate purchase; RCS and WhatsApp use their own onboarding. You can provision new numbers, attach them to agents, list existing numbers, retrieve messages for a number, and release numbers when no longer needed.

> **Note**
>
> **SMS compliance:** Receiving inbound SMS works out of the box. To send outbound SMS, US carriers require 10DLC (10-Digit Long Code) registration. Fill out the [registration form](https://agentphone.ai/register), it takes about 5 minutes and we submit everything to the carriers for you. If you can't figure it out, [schedule a call](https://calendar.app.google/kJQSTCyNYTphvCGm7) and we'll handle the registration for you. Voice does not require 10DLC registration, but outbound calls have separate funding, number-readiness, and concurrency requirements. See [Calls](/docs/documentation/guides/calls#create-outbound-call).

## Search available numbers

Preview purchasable numbers before buying one.

```
GET /v1/numbers/available
```

### Query parameters

| Parameter  | Description                                                                                                    |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| `areaCode` | 3-digit area code, e.g. `415`. The most broadly supported filter.                                              |
| `city`     | City name. Pair with `state`. Support varies by account.                                                       |
| `state`    | 2-letter state code, e.g. `CA`. Pair with `city` for consistent results; standalone support varies by account. |
| `zip`      | 5-digit ZIP. Support varies by account.                                                                        |
| `country`  | `US` or `CA`. Defaults to `US`.                                                                                |
| `limit`    | Max results, 1 to 30. Defaults to 10.                                                                          |

Searching with no criteria returns a page of whatever is in stock. Giving a Canadian area code searches Canada even though `country` defaults to `US`.

Criteria support varies by account and there is no single rule that holds everywhere:

* `areaCode` is the most broadly supported filter.
* `city`, `state` and `zip` work on some accounts and not others. Where they aren't supported they are **ignored rather than rejected**, so check what comes back instead of assuming the filter applied.
* `state` on its own is honored on some accounts and rejected with `400` on others. Pair it with `city` for consistent behavior.

> **Note**
>
> Number search isn't available on every account. When it isn't, this endpoint returns `404` (or `422` if it's enabled but not yet configured).
>
> That doesn't block you from buying a number. `POST /v1/numbers` accepts an `areaCode` and picks one for you on any account, so search is a convenience rather than a requirement. Contact support if you want search enabled.

### Example

```bash
curl "https://api.agentphone.ai/v1/numbers/available?areaCode=415&limit=5" \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY"
```

```json
{
  "data": [
    {
      "phoneNumber": "+14155550123",
      "city": "San Francisco",
      "state": "CA",
      "rateCenter": "SNFC CNTRL",
      "areaCode": "415"
    }
  ],
  "widened": false
}
```

`phoneNumber` is the only guaranteed field. `city`, `state` and `rateCenter` are `null` when the inventory source returns no per-number detail, so key off `phoneNumber` and treat the rest as best-effort labels.

`widened: true` means your exact criteria were out of stock and these are nearby alternatives instead. Check it before assuming you got what you asked for.

### Buying a number you found

Pass the same `phoneNumber` to `POST /v1/numbers`:

```bash
curl -X POST "https://api.agentphone.ai/v1/numbers" \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber": "+14155550123"}'
```

> **Warning**
>
> **Search results are not reserved.** This is live inventory, so a number can be bought by someone else between your search and your purchase. When that happens the purchase returns `409` with fresh alternatives in the same shape as the search response. Pick another and retry rather than treating it as fatal.

> **Warning**
>
> **Search and buy under the same account.** Each account and sub-account has its own inventory. Use the same account and `X-Sub-Account-Id` for `GET /v1/numbers/available` and `POST /v1/numbers`. Inventory and provisioning can differ across accounts; a candidate from another account or vendor may return `409` with `NUMBER_UNAVAILABLE`. If you don't need a specific number, send an `areaCode` instead and the sub-account gets the next available number in that code.

```python
import httpx

H = {"Authorization": f"Bearer {API_KEY}"}

def buy_in_area_code(area_code: str) -> dict:
    r = httpx.get(
        "https://api.agentphone.ai/v1/numbers/available",
        params={"areaCode": area_code, "limit": 5},
        headers=H,
    )
    r.raise_for_status()

    for entry in r.json()["data"]:
        resp = httpx.post(
            "https://api.agentphone.ai/v1/numbers",
            json={"phoneNumber": entry["phoneNumber"]},
            headers=H,
        )
        if resp.status_code == 409 and "NUMBER_UNAVAILABLE" in resp.text:
            continue  # a confirmed inventory conflict; try the next candidate
        resp.raise_for_status()
        return resp.json()

    raise RuntimeError(f"every candidate in {area_code} was taken")
```

Search is rate limited to 30 requests per minute per account, and purchase to 20.

## Create number

Provision a standard SMS/voice number, or use `type: "imessage"` for an [iMessage line](/docs/imessage). Standard numbers are available in the US and Canada. UK numbers (`UK` or `GB`) are available only on enabled accounts and are SMS-only; US/CA location filters do not apply to them.

```
POST /v1/numbers
```

### Request body

| Field          | Type             | Required | Default      | Description                                                                                                                                                                                                                       |
| -------------- | ---------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | string           | No       | `"sms"`      | `"sms"` for standard numbers or `"imessage"` for iMessage provisioning.                                                                                                                                                           |
| `imessageType` | string           | No       | `"outbound"` | For iMessage: `"inbound"` or `"outbound"`. See [provisioning responses and charges](/docs/imessage).                                                                                                                                   |
| `phoneNumber`  | string or null   | No       | null         | Exact number returned by the availability search. Search and purchase in the same account scope.                                                                                                                                  |
| `country`      | string           | No       | `"US"`       | Two-letter country code for the number (e.g., `"US"`, `"CA"`)                                                                                                                                                                     |
| `areaCode`     | string or null   | No       | null         | Preferred area code (US/CA only, e.g., `"415"`). If none are available in that area code, you'll get another number in the same country.                                                                                          |
| `agentId`      | string or null   | No       | null         | Optionally attach the number to an agent immediately                                                                                                                                                                              |
| `externalId`   | string or null   | No       | null         | Your own identifier for the number, unique within your account. Echoed on every number response and webhook; usable for lookup as `ext:<externalId>`.                                                                             |
| `expiresAt`    | datetime or null | No       | null         | Hard end date (ISO 8601, at least 5 minutes out, at most 10 years). The number is released automatically once it passes and you receive a `number.released` webhook. Move or clear it later with `PATCH /v1/numbers/{number_id}`. |

### Example

```bash
curl -X POST "https://api.agentphone.ai/v1/numbers" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "US",
    "areaCode": "415",
    "agentId": "agt_abc123"
  }'
```

```json
{
  "id": "num_xyz789",
  "phoneNumber": "+15551234567",
  "country": "US",
  "status": "active",
  "agentId": "agt_abc123",
  "outboundSms": "activating",
  "createdAt": "2025-01-15T10:45:00Z"
}
```

### Wait for outbound SMS to activate

A new US or CA number is attached to your messaging campaign right after purchase. That attachment takes a couple of minutes to confirm, and sends from the number are rejected with a `403` until it does. The `outboundSms` field on every number response tells you where the number stands:

| Value                   | Meaning                                                                                                                                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `activating`            | The campaign attachment is in progress. Sends return `403` until it completes, typically within 2 to 3 minutes of purchase.                                                                    |
| `enabled`               | The attachment is confirmed. Sends usually go through; a send in the first few minutes can fail with `4773` (see below).                                                                       |
| `registration_required` | This number needs registration or attachment to an approved campaign. Check the exact number and [registration account scope](/docs/sms-registration#approved-registration-but-sending-is-blocked). |

After `POST /v1/numbers` returns, poll `GET /v1/numbers/{number_id}` until `outboundSms` is `"enabled"` before sending messages from the number:

```bash
curl -X GET "https://api.agentphone.ai/v1/numbers/num_xyz789" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "id": "num_xyz789",
  "phoneNumber": "+15551234567",
  "status": "active",
  "outboundSms": "enabled"
}
```

Carriers pick up the new campaign mapping on their own schedule once the attachment is confirmed. This usually takes seconds, but a send fired in the first few minutes after `enabled` can occasionally fail with carrier error `4773` (campaign inactive for this destination). Retry that message once after about 30 seconds; it does not mean the number or campaign is misconfigured. Numbers outside the US and CA have no campaign requirement and are `enabled` from purchase.

## List numbers

List all phone numbers for this project.

```
GET /v1/numbers
```

### Query parameters

| Parameter | Type    | Required | Default | Description                           |
| --------- | ------- | -------- | ------- | ------------------------------------- |
| `limit`   | integer | No       | 20      | Number of results to return (max 100) |
| `offset`  | integer | No       | 0       | Number of results to skip (min 0)     |

### Example

```bash
curl -X GET "https://api.agentphone.ai/v1/numbers?limit=10&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Look up a number

Check line-type intelligence for any phone number before you message it: whether it is a mobile, landline, or VoIP line, its country, and whether the handset supports RCS. Useful for keeping agents from spending messages on numbers that can never receive them.

```
GET /v1/numbers/lookup
```

Billed at \$0.009 per number looked up.

### Query parameters

| Parameter      | Type   | Required | Description                                                    |
| -------------- | ------ | -------- | -------------------------------------------------------------- |
| `phoneNumbers` | string | Yes      | Comma-separated E.164 numbers to look up, up to 25 per request |

### Example

```bash
curl -X GET "https://api.agentphone.ai/v1/numbers/lookup?phoneNumbers=%2B14155551234,%2B15551230000" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "data": [
    {
      "phoneNumber": "+14155551234",
      "lineType": "mobile",
      "country": "US",
      "rcsEnabled": true
    },
    {
      "phoneNumber": "+15551230000",
      "lineType": "landline",
      "country": "US",
      "rcsEnabled": false
    }
  ]
}
```

`lineType` is `"mobile"`, `"landline"`, or `"voip"`; fields are `null` when a number cannot be resolved. Accounts with a negative balance receive a `402` before any lookup runs.

## Update number

Change a number's own fields. Only the fields you send are changed; `null` clears a field, omitting it leaves it alone. Unsupported fields are rejected with `422`.

```
PATCH /v1/numbers/{number_id}
```

### Request body

| Field        | Type             | Description                                                                                                                                                                                                |
| ------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `externalId` | string or null   | Your own identifier for the number, unique within your account.                                                                                                                                            |
| `expiresAt`  | datetime or null | Hard end date. Set it to schedule an automatic release, move it later to extend the number, or send `null` to keep the number indefinitely. Must be at least 5 minutes and at most 10 years in the future. |

### Expiring numbers

If you sell or lease numbers for a fixed period, set `expiresAt` when you buy the number and extend it on each renewal. You don't need to remember to call `DELETE`:

1. Every few minutes a sweep releases any active number whose `expiresAt` has passed, exactly as `DELETE /v1/numbers/{number_id}` would.
2. A `number.released` webhook with `reason: "expired"` is sent to the number's webhook (the agent's if it has one, otherwise your project default).
3. Messages and conversation history are kept; the number itself is gone and cannot be recovered.

Monthly billing is unchanged: a number with an expiry bills like any other until it is released. Expiry is not available on iMessage area-code orders, which are fulfilled later; set it with `PATCH` once the number is delivered.

### Example

```bash
# Set the lease end to 2026-12-15 (send a later date to extend, null to remove)
curl -X PATCH "https://api.agentphone.ai/v1/numbers/num_xyz789" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresAt": "2026-12-15T00:00:00Z"}'
```

## Enable or disable inbound calls

Turn incoming calls on or off for one number. With inbound calls disabled the number rejects calls before they are answered: no call record is created and nothing is billed. Messaging in both directions and outbound calls are unaffected.

```
PATCH /v1/numbers/{number_id}/inbound-calls
```

### Request body

| Field     | Type    | Required | Description                                                             |
| --------- | ------- | -------- | ----------------------------------------------------------------------- |
| `enabled` | boolean | Yes      | `false` rejects inbound calls on this number, `true` answers them again |

### Example

```bash
curl -X PATCH "https://api.agentphone.ai/v1/numbers/num_xyz789/inbound-calls" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'
```

```json
{
  "success": true,
  "inboundCallsEnabled": false
}
```

The setting survives agent changes. Attaching the number to an agent, or editing that agent, keeps inbound calls off, so you can hold an agent assignment on a number for messaging only. Disable before you attach and there is no window in which a call can be answered.

Use the number's `id` as returned by the API in the path, not the phone number itself. Numbers that cannot enforce the setting return `422`: messaging-only lines, and most numbers that route calls to your own SIP trunk, which manage inbound calls on your platform instead. Numbers under a sub-account need the `X-Sub-Account-Id` header alongside your main API key.

## Delete number (release)

Release (delete) a phone number.

For a standard number, this action:

1. Releases the number back to the carrier pool
2. Marks the number as `"released"` in the database
3. Keeps all messages and conversation history for audit purposes

> **Warning**
>
> This action is irreversible. The number cannot be recovered once released.

```
DELETE /v1/numbers/{number_id}
```

### Example

```bash
curl -X DELETE "https://api.agentphone.ai/v1/numbers/num_xyz789" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

For iMessage, release deactivates the line immediately; provider teardown is handled separately. Check Billing for the subscription state and contact support if a released line still appears billable. Deleting or detaching an **agent** does not release its number.

## Get messages for number

Get messages for a specific phone number. Supports cursor-based pagination via `before`/`after` timestamps. This endpoint uses `from_` for the sender, unlike the webhook field `data.from` or conversation-history field `fromNumber`.

```
GET /v1/numbers/{number_id}/messages
```

### Query parameters

| Parameter | Type                      | Required | Default | Description                                      |
| --------- | ------------------------- | -------- | ------- | ------------------------------------------------ |
| `limit`   | integer                   | No       | 50      | Number of messages to return (max 200)           |
| `before`  | string (datetime) or null | No       | null    | Return messages before this timestamp (ISO 8601) |
| `after`   | string (datetime) or null | No       | null    | Return messages after this timestamp (ISO 8601)  |

### Example

```bash
curl -X GET "https://api.agentphone.ai/v1/numbers/num_xyz789/messages?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "data": [
    {
      "id": "msg_001",
      "from_": "+15559876543",
      "to": "+15551234567",
      "body": "Hi, I need help with my order",
      "receivedAt": "2025-01-15T12:00:00Z"
    }
  ],
  "hasMore": false
}
```