---
title: "Purchase a number"
method: POST
path: "/api/v1/numbers"
tags: ["Phone Numbers"]
---

# Purchase a number

`POST /api/v1/numbers`

## Request

- Base URL: `https://api.callnode.io`
- URL: `https://api.callnode.io/api/v1/numbers`
- Auth: API key in header `X-Api-Key` and API key in header `X-Timestamp` and API key in header `X-Signature`

Purchases a number found via search and bills your wallet for the first
month at your plan's rate (no setup fee; an included Pro Bundle number
costs nothing). Voice and messaging webhooks can be
provisioned in the same call, so the number is fully wired the moment it
activates — no dashboard visit needed.

`type` is optional and derived from the number itself (8NN prefixes are
toll-free). Purchasing is idempotent on the number: re-purchasing a number
you already own returns `200` with the existing record instead of billing
twice. A number you previously released is automatically restored from the
recycle bin rather than re-ordered.

A2P messaging registration provisions asynchronously after purchase — the
number's `messaging.ready` flag flips to `true` when it can send SMS/MMS.

## Request body

- PurchaseNumberRequest
  - `phoneNumber` string, required: Plus sign followed by 7–15 digits.
  - `type` 'local' | 'tollfree': Optional — derived from the number itself when omitted.
  - `friendlyName` string
  - `tags` string[]: Your own labels for the number. Trimmed, de-duplicated case-insensitively, returned on every number, filterable on the dashboard. PATCH replaces the whole list; send [] to clear.
  - `voice` VoiceConfig: Voice configuration — how inbound calls to this number are answered. Three modes: a conversational WEBHOOK agent (webhookUrl set), SIP DELIVERY to a literal endpoint (sipUri set), or SIP DELIVERY to one of your saved trunks (trunkId set). sipUri and trunkId are mutually exclusive — setting one clears the other, and either takes precedence over the webhook. Wholesale numbers must always keep at least one mode — a change that would leave none is rejected with 400.
    - `webhookUrl` string, uri, nullable: Invoked when a call connects and once per caller utterance. Empty/null disconnects the webhook.
    - `sipUri` string, nullable: BYOC / wholesale SIP delivery: when set, inbound calls bridge straight to this sip: endpoint and the webhook never runs. Format sip:user@host[:port][;transport=udp|tcp|tls], public hosts only. Null switches the number back to webhook mode (for wholesale numbers, only allowed when a webhookUrl is present).
    - `srtp` boolean: Encrypt the audio on the sipUri delivery leg (SRTP with SDES key exchange, AES_CM_128_HMAC_SHA1_80 / AES_CM_128_HMAC_SHA1_32; no fallback to plain RTP). Accepted only when sipUri ends in ;transport=tls — otherwise 400. Cleared automatically when the address changes to UDP/TCP or the number switches to trunk delivery. See /docs/sip-trunking#byoc-srtp.
    - `trunkId` string, nullable: Deliver inbound calls to one of your SIP trunks. A trunk with an egress host is dialed at that host; a REGISTRATION trunk is resolved to wherever your PBX is currently registered, at call time — the way to receive DID calls on equipment behind NAT. If the trunk is not registered when a call arrives, the call falls back to the number's webhook agent (if configured). Null clears trunk delivery. Mutually exclusive with sipUri.
    - `realtime` object, nullable: Realtime WebSocket mode (see /docs/realtime): CallNode opens a WebSocket to `url` for every call to this number and streams transcripts (and, on request, caller audio); you stream audio back, transfer or hang up. The webhook stays the fallback when the socket cannot be reached. Null turns it off. The auth secret is write-only (reads return valueSet).
      - `url` string, uri, required: wss:// URL on a public host
      - `auth` WebhookAuth: How CallNode authenticates to your webhook endpoint.
        - `type` 'none' | 'basic' | 'bearer' | 'header', required
        - `headerName` string: Header name when `type` is `header`.
        - `value` string: The secret, token, or `user:pass` pair, depending on `type`.
      - `voice` string, nullable: Voice for `say` frames (text mode): an id from GET /api/v1/numbers/voices. Null = the platform default voice.
    - `webhookAuth` WebhookAuth: How CallNode authenticates to your webhook endpoint.
      - `type` 'none' | 'basic' | 'bearer' | 'header', required
      - `headerName` string: Header name when `type` is `header`.
      - `value` string: The secret, token, or `user:pass` pair, depending on `type`.
    - `webhookTimeout` integer: Webhook response timeout in milliseconds.
  - `messaging` MessagingConfig: Messaging webhook configuration for inbound SMS/MMS to this number.
    - `webhookUrl` string, uri, nullable
    - `webhookAuth` WebhookAuth: How CallNode authenticates to your webhook endpoint.
      - `type` 'none' | 'basic' | 'bearer' | 'header', required
      - `headerName` string: Header name when `type` is `header`.
      - `value` string: The secret, token, or `user:pass` pair, depending on `type`.
    - `enabled` boolean

## Response `200`

Idempotent replay — you already own this number; nothing was billed.

- PurchaseNumberResponse
  - `number` PhoneNumber
    - `id` string
    - `phoneNumber` string: Plus sign followed by 7–15 digits.
    - `type` 'local' | 'tollfree'
    - `friendlyName` string, nullable
    - `status` string
    - `purchaseSource` 'web' | 'api': How the number was purchased — via the dashboard (`web`) or this API (`api`).
    - `tags` string[]: Your own labels for the number. Trimmed, de-duplicated case-insensitively, returned on every number, filterable on the dashboard. PATCH replaces the whole list; send [] to clear.
    - `voice` object
      - `webhookUrl` string, uri, nullable
      - `webhookAuth` WebhookAuthStatus, nullable: How CallNode authenticates to your webhook. The secret itself is write-only and never returned.
        - `type` 'basic' | 'bearer' | 'header'
        - `headerName` string
        - `valueSet` boolean
      - `webhookTimeout` integer
      - `sipUri` string, nullable: SIP delivery target (BYOC / wholesale). Null when the number runs in webhook mode.
      - `srtp` boolean: Whether the sipUri delivery leg offers SRTP (only ever true beside a ;transport=tls address).
      - `trunkId` string, nullable: SIP trunk this number delivers inbound calls to. Null unless trunk delivery is configured.
      - `realtime` object, nullable: Realtime WebSocket mode (see /docs/realtime). Null when the number runs the per-turn webhook loop.
        - `url` string, uri: wss:// endpoint CallNode connects to per call
        - `auth` WebhookAuthStatus, nullable: How CallNode authenticates to your webhook. The secret itself is write-only and never returned.
          - `type` 'basic' | 'bearer' | 'header'
          - `headerName` string
          - `valueSet` boolean
        - `voice` string, nullable: Voice id used for say frames; null = platform default
    - `messaging` object
      - `webhookUrl` string, uri, nullable
      - `webhookAuth` WebhookAuthStatus, nullable: How CallNode authenticates to your webhook. The secret itself is write-only and never returned.
        - `type` 'basic' | 'bearer' | 'header'
        - `headerName` string
        - `valueSet` boolean
      - `enabled` boolean
      - `ready` boolean, nullable: Whether A2P messaging registration has completed for this number. Provisions asynchronously after purchase.
    - `monthlyRate` number
    - `perMinuteRate` number
    - `includedInSubscription` boolean
    - `createdAt` string, date-time
    - `updatedAt` string, date-time
  - `charged` number: Amount deducted from your wallet. 0 for included-in-plan numbers and idempotent replays.
  - `breakdown` object, nullable
    - `setupFee` number
    - `monthlyFee` number
    - `total` number
  - `wallet` object, nullable
    - `previousBalance` number
    - `newBalance` number
  - `includedInSubscription` boolean
  - `idempotencyReplay` boolean: True when you already owned this number and nothing was billed.

## Other responses

- `201`: Number purchased, billed, and configured.
- `400`: Malformed request or failed validation.
- `401`: Missing, unknown, or badly signed credentials — check the HMAC and the timestamp skew.
- `402`: Insufficient wallet balance (`insufficient_balance`). The body includes `required`, `current`, and `needed` amounts.
- `409`: The number is not available for purchase (`number_unavailable`).
- `502`: The carrier order failed (`provider_error`). Nothing was billed.

---

[API](https://skmtc.dev/callnode/apis/callnode-outbound-api.md) · [All operations](https://skmtc.dev/callnode/apis/callnode-outbound-api/llms.txt) · [OpenAPI document](https://skmtc.dev/callnode/apis/callnode-outbound-api/revisions/c7da6ad06509?raw)
