voepy

Phone numbers

Buy, configure, and release the DIDs your calls flow through.

All paths live under /v1/. Authentication is Authorization: Bearer <api-key> on every request.

Number types

TypeNotes
localA normal geographic number (e.g. +1 512 …). Cheapest, available in most countries.
toll_freeUS/CA toll-free (+1 800/833/844/855/866/877/888 …). Free for the caller; you pay both directions.
nationalA non-geographic national-coverage number. Availability varies by country.
mobileA mobile-prefix DID. Required in some countries before SMS works.

The number_type you pick at order time affects pricing (see the quote in the order response). Some types are unavailable in some countries — check GET /v1/numbers/available before you commit.

Search the inventory

GET /v1/numbers/available
  ?country_code=US
  &number_type=local
  &national_destination_code=512    # area code; optional
  &locality=Austin                   # city name; optional
  &features=voice,sms                # comma list
  &limit=25
{
  "data": [
    {
      "phone_number": "+15125550100",
      "country_code": "US",
      "region": "TX",
      "locality": "Austin",
      "number_type": "local",
      "capabilities": ["voice", "sms"],
      "pricing": {
        "activation_fee_cents": 100,
        "monthly_fee_cents": 150
      }
    }
  ],
  "meta": { "count": 25 }
}

Pricing is your customer price (after markup), so what you see is what we'll debit.

Reserve before you buy (optional)

If you want to hold a candidate set while a user picks one in your UI, reserve them first. Reservations expire automatically.

POST /v1/number-reservations
Content-Type: application/json

{
  "phone_numbers": ["+15125550100", "+15125550101"]
}

Response includes expires_at. The numbers can't be bought by anyone else (including other voepy tenants) until then.

Order a number

POST /v1/numbers/order
Content-Type: application/json
Idempotency-Key: <uuid>

{
  "phone_numbers": ["+15125550100"],
  "country_code": "US",
  "number_type": "local"
}

What happens:

  1. We quote the order against the current pricing tier (NRC + MRC).
  2. We debit your balance for the NRC up front. If you can't cover it, you get 402 balance_insufficient and nothing is ordered.
  3. We submit the order to the upstream carrier. The number row appears in pending status immediately.
  4. When activation completes (usually seconds, sometimes minutes), the row flips to active and you'll receive a number_order.completed webhook.

The response is a list of PhoneNumber resources:

[
  {
    "id": "f3b0a9b7-…",
    "e164": "+15125550100",
    "country_code": "US",
    "number_type": "local",
    "capabilities": ["voice", "sms"],
    "status": "pending",
    "monthly_price_cents_customer": 150,
    "activation_price_cents_customer": 100,
    "connection_id": null,
    "ordered_at": "2026-05-12T14:32:18.000Z",
    "activated_at": null,
    "released_at": null
  }
]

Always send Idempotency-Key on /numbers/order. Retrying without it can charge you twice for the same number.

List your numbers

GET /v1/numbers?status=active&limit=50

Filter by status (pending, active, released), number_type, or connection_id.

Configure a number

PATCH /v1/numbers/{id} updates voice / messaging settings. Only include the fields you want to change.

PATCH /v1/numbers/f3b0a9b7-…
Content-Type: application/json

{
  "customer_reference": "primary-support-line",
  "cnam_lookup_enabled": true,
  "cnam_display_name": "Yourco Sales",
  "messaging_enabled": true,
  "connection_id": "9a8d1c…"
}
FieldWhat it does
customer_referenceFree-form tag (≤120 chars). Shown back to you on the resource and in admin views.
cnam_lookup_enabledLook up caller-id name on inbound. Adds per-call cost (see pricing).
cnam_display_nameThe 15-char name displayed to callees on outbound when CNAM is supported.
tech_prefix_enabledAdd the technical prefix on SIP signaling. Most tenants leave this off.
inbound_call_screeningdisabled, flag_calls (flag suspicious in headers), or reject_calls (refuse them outright).
messaging_enabledAttach the tenant's messaging profile so the DID can send/receive SMS.
connection_idReassign the DID to a different connection (sub-customer scope). null to detach.

You'll get 409 number_not_yet_active if you try to patch a pending number — retry once it activates.

Release a number

DELETE /v1/numbers/{id}

We release the DID upstream and stop billing the monthly fee at end of the current cycle (no refund for partial months). The row stays queryable in released state for audit / dispute purposes.

Number lookup

Look up metadata for any number (yours or not). Useful for caller-id enrichment.

GET /v1/numbers/lookup?phone_number=+15125550100&type=carrier

type is carrier (carrier name + network type) or caller-name (CNAM). Both are billed per lookup.

Porting numbers in

Bringing a number you already own at another carrier:

# 1. Pre-flight: are these numbers portable?
GET /v1/numbers/portability-check?phone_numbers=+15125550100,+15125550101

# 2. Pull the CSR (Customer Service Record) to confirm losing-carrier data
POST /v1/numbers/csr-coverage
{ "phone_numbers": ["+15125550100"] }

# 3. Submit the porting order
POST /v1/numbers/porting-orders
{
  "phone_numbers": ["+15125550100"],
  "user_feedback": { "comment": "Moving from CarrierX" },
  "documents": ["<doc-id-from-step-4>"]
}

# 4. Upload supporting docs (LOA, recent bill)
POST /v1/numbers/porting-documents
Content-Type: multipart/form-data
... <file uploads>

Porting takes business days, not seconds. Status transitions fire webhooks (porting_order.*). Track progress at GET /v1/numbers/porting-orders.

Emergency 911 (US/CA)

If you originate emergency-capable calls, register an address per DID:

POST /v1/emergency-addresses
{
  "street_address": "1 Example St",
  "extended_address": "Suite 200",
  "locality": "Austin",
  "administrative_area": "TX",
  "postal_code": "78701",
  "country_code": "US"
}

Then attach it via an emergency endpoint that maps DID → address. Required for compliance — uncovered numbers will reject 911 traffic.

Connections (sub-customer scope)

A connection groups one or more numbers under a shared webhook / configuration scope. Default tenants have one connection. Multi-brand tenants can create more and assign DIDs to them via PATCH /v1/numbers/{id} with connection_id.

GET /v1/connection         # the tenant's default connection
GET /v1/connections        # all connections you own

Webhook subscriptions can be scoped to a single connection (see Webhooks).

Next

  • Calls — now that you have a DID, place a call from it.
  • Webhooks — react to inbound calls on these numbers.
  • Billing — how monthly fees show up on your invoice.