# Buy and release a number

Buying a number takes two calls: search a country for what is on sale, then order the one you want. Everything here needs an API key with the `numbers` scope, and the first purchase in an organization needs identity verification.

## Search a country

The search is always scoped to one country, so `country_code` is required. Narrow it further with `number_type`, `capabilities`, or a `prefix` of national digits.

<!-- bird:tabs typescript,python,go,php,curl -->

<!-- bird:snippet numbers.available.list -->

```typescript
// The search is always country-scoped, so country_code is required.
const page = await bird.numbers.available.list({
  country_code: "GB",
  capabilities: ["sms", "voice"],
});
for (const candidate of page.data) {
  console.log(candidate.number, candidate.number_type);
}
```

<!-- bird:snippet numbers.available.list -->

```python
# The search is always country-scoped, so country_code is required.
page = client.numbers.available.list(country_code="GB", capabilities=["sms", "voice"])
for candidate in page.data:
    print(candidate.number, candidate.number_type)
```

<!-- bird:snippet numbers.available.list -->

```go
for candidate, err := range client.Numbers.Available.List(context.Background(), bird.NumbersAvailableListParams{
	CountryCode:  "GB",
	Capabilities: []string{"sms", "voice"},
}) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(candidate.Number, candidate.NumberType)
}
```

<!-- bird:snippet numbers.available.list -->

```php
// The search is always country-scoped, so country_code is required.
$page = $bird->numbers->available->list([
    'country_code' => 'GB',
    'capabilities' => ['sms', 'voice'],
]);
foreach ($page as $candidate) {
    echo $candidate->getNumber(), ' ', $candidate->getNumberType(), "\n";
}
```

```bash
curl "https://us1.platform.bird.com/v1/numbers/available?country_code=GB" \
  -H "Authorization: Bearer bk_us1_..."
```

<!-- /bird:tabs -->

Use the regional host that matches your key's `bk_{region}_` prefix: `https://us1.platform.bird.com` or `https://eu1.platform.bird.com`.

Each result carries only what you need to choose one:

```json
{
  "data": [
    {
      "number": "+447700900123",
      "country_code": "GB",
      "number_type": "mobile",
      "capabilities": ["sms", "voice"]
    }
  ],
  "next_cursor": null,
  "prev_cursor": null
}
```

Bird's own stock is returned first and pages normally. The last page can include numbers a carrier is offering live, so a number listed a moment ago may already be gone by the time you order it.

## Order the number

Pass a number from the search to [`POST /v1/numbers/orders`](/docs/api/reference/create-numbers-order). Send an `Idempotency-Key` so a retry cannot buy twice.

<!-- bird:tabs typescript,python,go,php,curl -->

<!-- bird:snippet numbers.orders.create -->

```typescript
const order = await bird.numbers.orders.create({ number: "+447700900201" });
// Most orders finish inside the request. One that has to wait on a carrier
// comes back without a number_id. Poll it until it is completed or failed.
if (order.status === "completed") {
  console.log("held as", order.number_id);
} else {
  console.log("still", order.status, "; poll", order.id);
}
```

<!-- bird:snippet numbers.orders.create -->

```python
order = client.numbers.orders.create(number="+447700900201")
# Most orders finish inside the request. One that has to wait on a carrier
# comes back without a number_id. Poll it until completed or failed.
if order.status == "completed":
    print("held as", order.number_id)
else:
    print("still", order.status, "; poll", order.id)
```

<!-- bird:snippet numbers.orders.create -->

```go
order, err := client.Numbers.Orders.Create(context.Background(), bird.NumbersOrdersCreateParams{
	Number: "+447700900201",
})
if err != nil {
	log.Fatal(err)
}
// An order that has to wait on a carrier comes back without a NumberId.
// Poll it until it is completed or failed.
fmt.Println(order.Status, order.Id)
```

<!-- bird:snippet numbers.orders.create -->

```php
$order = $bird->numbers->orders->create(
    (new NumbersOrderCreate())->setNumber('+447700900201'),
);
// Most orders finish inside the request. One that has to wait on a carrier
// comes back without a number_id. Poll it until it is completed or failed.
if ($order->getStatus() === 'completed') {
    echo 'held as ', $order->getNumberId(), "\n";
} else {
    echo 'still ', $order->getStatus(), '; poll ', $order->getId(), "\n";
}
```

```bash
curl -X POST "https://us1.platform.bird.com/v1/numbers/orders" \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"number": "+447700900123"}'
```

<!-- /bird:tabs -->

Most orders finish inside the request and answer `201` with the number already yours:

```json
{
  "id": "nor_01m0da22b0e39anhzyhtw3gzdg",
  "number": "+447700900123",
  "country_code": "GB",
  "number_type": "mobile",
  "status": "completed",
  "number_id": "nda_7eqywfwzxwa1za9n8wp7e1xkr8",
  "failure_reason": null,
  "completed_at": "2026-08-19T15:25:56.479320Z",
  "created_at": "2026-08-19T15:25:56.448051Z",
  "updated_at": "2026-08-19T15:25:56.479320Z"
}
```

`number_id` is the handle for everything after this: reading the number, and releasing it.

## Poll an order that did not finish

An order that has to wait on a carrier answers `202` instead, with `number_id` still `null`. Read it back until `status` is `completed` or `failed`.

<!-- bird:tabs typescript,python,go,php,curl -->

<!-- bird:snippet numbers.orders.get -->

```typescript
const order = await bird.numbers.orders.get("nor_01krdgeqcxet5s7t44vh8rt9mg");
// failure_reason says what went wrong, and only ever on a failed order.
console.log(order.status, order.failure_reason ?? "");
```

<!-- bird:snippet numbers.orders.get -->

```python
order = client.numbers.orders.get("nor_01krdgeqcxet5s7t44vh8rt9mg")
# failure_reason says what went wrong, and only ever on a failed order.
print(order.status, order.failure_reason or "")
```

<!-- bird:snippet numbers.orders.get -->

```go
order, err := client.Numbers.Orders.Get(context.Background(), "nor_01krdgeqcxet5s7t44vh8rt9mg")
if err != nil {
	log.Fatal(err)
}
// FailureReason says what went wrong, and only ever on a failed order.
fmt.Println(order.Status)
```

<!-- bird:snippet numbers.orders.get -->

```php
$order = $bird->numbers->orders->get('nor_01krdgeqcxet5s7t44vh8rt9mg');
// failure_reason says what went wrong, and only ever on a failed order.
echo $order->getStatus(), ' ', $order->getFailureReason() ?? '', "\n";
```

```bash
curl "https://us1.platform.bird.com/v1/numbers/orders/nor_01m0da22b0e39anhzyhtw3gzdg" \
  -H "Authorization: Bearer bk_us1_..."
```

<!-- /bird:tabs -->

An order moves through `charging`, `ordering`, and `pending` before it settles. On `failed`, `failure_reason` says what went wrong in plain terms. A setup fee already taken is not refunded, so a failed order can leave a charge behind; contact support if that happens.

## Release a number

Releasing stops the monthly charge and the number stops working for you. Only a `dedicated` number can be released, and a released number does not go straight back on sale.

<!-- bird:tabs typescript,python,go,php,curl -->

<!-- bird:snippet numbers.release -->

```typescript
// Releasing stops the monthly charge and the number stops working for you.
// Only a dedicated number can be released; a shared one answers E14002.
await bird.numbers.release("nda_01krdgeqcxet5s7t44vh8rt9mg");
```

<!-- bird:snippet numbers.release -->

```python
# Releasing stops the monthly charge and the number stops working for you.
# Only a dedicated number can be released; a shared one answers E14002.
client.numbers.release("nda_01krdgeqcxet5s7t44vh8rt9mg")
```

<!-- bird:snippet numbers.release -->

```go
// Only a dedicated number can be released; a shared one answers E14002.
if err := client.Numbers.Release(context.Background(), "nda_01krdgeqcxet5s7t44vh8rt9mg"); err != nil {
	log.Fatal(err)
}
```

<!-- bird:snippet numbers.release -->

```php
// Releasing stops the monthly charge and the number stops working for you.
// Only a dedicated number can be released; a shared one answers E14002.
$bird->numbers->release('nda_01krdgeqcxet5s7t44vh8rt9mg');
```

```bash
curl -X DELETE "https://us1.platform.bird.com/v1/numbers/nda_7eqywfwzxwa1za9n8wp7e1xkr8" \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Idempotency-Key: $(uuidgen)"
```

<!-- /bird:tabs -->

A successful release answers `204` with no body.

## When an order is refused

Four refusals cover almost every failed purchase.

`402` with [`E03000`](/docs/api/errors/E03000) means the wallet cannot cover the number. Top up and order again. No order is created, so nothing is charged.

`412` means the organization has not completed the identity verification a first purchase requires. Complete it, then retry.

`409` with [`E14000`](/docs/api/errors/E14000) means the number went while you were choosing it. Search again and pick another.

`409` with [`E14001`](/docs/api/errors/E14001) means too many of your orders are already in flight. Let one finish, then order again.

## Next steps

- [Numbers overview](/docs/guides/numbers/overview) explains what the fields on a number mean.
- [Numbers API reference](/docs/api/reference/create-numbers-order) documents every operation and field.
- [Idempotency](/docs/guides/idempotency) explains how keys make a retried order safe.