# Numbers

Source: https://joinsimplesms.com/docs/numbers
Index: https://joinsimplesms.com/llms.txt

## Search available numbers

`GET /v1/numbers/available?area_code=415`

Returns up to 5 available numbers. In the sandbox this is a deterministic fake
inventory; with live access it searches real US/Canada inventory across 200+
area codes.

## Purchase a number

`POST /v1/numbers` with `{ "phone_number": "+1..." }` (optionally
`"customer_id": "cus_..."` to assign it to one of your
[customers](/docs/customers))

Adds the number to your account (quota applies: default 2 live numbers, 3
sandbox). Returns the Number object. Emits a `number.purchased` event.
Send an `Idempotency-Key` header and a retried purchase returns the original
`201`. Without one, the retry gets "You already own this number."

## List your numbers

`GET /v1/numbers` (`?customer_id=cus_...` for one customer's). Each
number carries `customer_id` (`null` when unassigned) and `sender`
(below). `GET /v1/numbers/+14155550132` returns one.

## Sender status

A live US number works the moment you buy it, with one restriction: until it
is registered with the carriers it is **test only**. It can text **your
verified numbers** (your own phone, and any number you verify under Billing →
Verified numbers), and nobody else. That is enough to build and test your
integration end to end while the registration is in review. Once the number
is linked to an approved [registration](/docs/compliance) it can text anyone.

Every number carries its standing:

```json
"sender": {
  "state": "pending",
  "registration_id": "reg_a1B2c3D4e5F6",
  "reason": null,
  "updated_at": "2026-10-04T16:20:00.000Z"
}
```

| State | Meaning | Can text |
| --- | --- | --- |
| `test_only` | No registration has been submitted for it | Your verified numbers |
| `pending` | A registration is in carrier review, or approved and being linked | Your verified numbers |
| `active` | Linked to an approved registration | Anyone |
| `action_needed` | Something needs you: the registration was rejected, its website check fails, or the link failed. `reason` says exactly what to do | Your verified numbers |

`sender` is `null` on sandbox numbers (they never reach a real phone)
and on numbers that need no registration.

You do not have to do anything to move a number along. Submit one
registration and every live number on the account follows it: `pending`
while the carriers review, then linked and `active` after approval, usually
within minutes. A number bought later links on its own. Each change emits
`number.sender_updated` (`data`: `phone_number`, `state`,
`previous_state`, `registration_id`, `reason`).

A send to anyone else from a number that is not `active` answers **403**
`sender_not_registered` with an `X-SimpleSMS-Sender-State` header and a
message that says what to do. Nothing is sent and nothing is charged.
Broadcasts, scheduled sends and automations follow the same rule: those
recipients fail with `sender_not_registered`, never silently.

### Choose a registration for a number

`POST /v1/numbers/+14155550132/registration` with
`{ "registration_id": "reg_..." }`

Only needed when your account has more than one approved registration (the
number is `action_needed` until you choose), or to retry a link that
failed. You can also pass `registration_id` when you buy the number. One
registration holds up to **49 numbers**, the most carriers allow; attaching a
50th answers `409`. Returns the number with its new `sender`.

## Assign a number to a customer

`PATCH /v1/numbers/+15005550132` with `{ "customer_id": "cus_..." }`, or
`{ "customer_id": null }` to unassign. Messages to and from the number are
attributed to that customer from then on; earlier messages keep theirs.

## Release a number

`DELETE /v1/numbers/+15005550132`

Marks the number released (live mode removes it from its registration, then
disconnects it at the carrier). Rate limited to 10 releases per 30 minutes.
Emits `number.released`.

> Live number purchase and release require live access; sandbox
> numbers work for everyone immediately.

## Buy in bulk

`POST /v1/numbers/bulk-purchase`

```json
{ "area_code": "415", "quantity": 10, "pool_id": "pool_a1B2c3D4e5F6", "tags": ["spring"] }
```

Up to 50 per call. Your number limit is checked for the whole `quantity`
first: if it doesn't fit you get `429 quota_exceeded` saying how many more
you can add, and nothing is bought. After that, a number the carrier refuses
is listed in `failed` and replaced from spare inventory; `shortfall` is how
many the area code couldn't supply. Test keys mint sandbox numbers.

## Tags, labels, and bulk actions

Every number carries an optional `label` ("Front desk") and up to 20
`tags` (lowercased). Filter on them anywhere:
`GET /v1/numbers?tag=spring&pool=pool_...&mode=live&q=front`.

`POST /v1/numbers/bulk` applies one action to up to 1000 numbers:

| `action` | Extra field |
| --- | --- |
| `add_tags` / `remove_tags` | `tags: [...]` |
| `set_label` | `label` (`null` clears) |
| `add_to_pool` / `remove_from_pool` | `pool_id` |
| `release` | `confirm: "RELEASE <count>"` |

The response is always one result per id you sent, so a batch where 3 of 143
ids were wrong tells you which 3:

```json
{
  "object": "bulk_result", "action": "add_tags",
  "requested": 143, "succeeded": 140, "failed": 3,
  "results": [{ "id": "+14155550132", "ok": true }, { "id": "+1415555", "ok": false, "error": "invalid_id" }]
}
```

Per-id errors: `invalid_id`, `duplicate`, `not_found`, `too_many_tags`,
`mode_mismatch` (a test key releasing a live number), `carrier_error`.

**Release is guarded.** Up to 100 per call, and `confirm` must be
`"RELEASE <number of ids sent>"`, so a script that built the wrong list
fails before anything is disconnected. Released live numbers go back to
carrier inventory and may not be recoverable.

## Sender pools

A pool is a named group of your numbers. Send with `"from": "pool_..."`
and SimpleSMS picks a member, **the same one for each recipient every time**, so
replies stay in one thread on their phone. Live keys draw only live members;
test keys only sandbox members.

```bash
curl https://api.joinsimplesms.com/v1/numbers/pools \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Support"}'
```

`GET`/`PATCH`/`DELETE /v1/numbers/pools/:id`, and
`POST /v1/numbers/pools/:id/clone` copies a pool (same description and
members, new id). Numbers can sit in several pools. Deleting a pool keeps its
numbers. Membership is set with the bulk actions above.

## Export

`GET /v1/numbers/export` returns CSV (`phone_number, mode, label, tags,
pools, pool_ids, created_at`) and takes the same filters as the list. The
console exports a filter or an exact selection.
