BetaThe V4 API is in beta — endpoints and functionality may change.

Number provisioning

Browse, purchase, track, and remove Blooio P2P numbers over the API — the full provisioning lifecycle with API keys or OAuth.

The Number provisioning API lets you browse Blooio number inventory, buy new P2P lines, track provisioning, and remove (unsubscribe) lines you own — the entire lifecycle you can drive from the dashboard, now over the API. Everything is scoped under the Blooio channel type: /v4/channels/blooio.

The full lifecycle — request access, browse, purchase, provision, track, and remove. Click a step for details; drag to pan, use the controls to zoom.

Note This feature is gated. A KYC-verified organization admin must request access from Settings → Number Purchase API, and a Blooio admin approves it before the endpoints turn on. Until then every endpoint returns 403 feature_not_enabled.

Enable access

Access is granted per organization:

  1. Verify your organization's KYC (required first).
  2. An admin opens Settings → Number Purchase API and submits the request (what you're building and your website). This opens an internal ticket.
  3. A Blooio admin reviews and approves. Your org's numbers:read / numbers:manage endpoints then start responding.

Authenticate

Both credential types work:

  • API keysAuthorization: Bearer bl_live_.... Full organization access.
  • OAuth app tokens — must carry the right scope:
  • numbers:read — list available inventory and read purchase status.
  • numbers:manage — purchase and remove lines.

See Calling the API with OAuth tokens for the app flow.

1. Browse available numbers

List masked inventory, or pass one or more area_code values to quote specific area codes.

# Available shared lines
curl 'https://api.blooio.com/v4/channels/blooio/available?type=shared' \
 -H 'Authorization: Bearer bl_live_...'

# Quote specific dedicated area codes
curl 'https://api.blooio.com/v4/channels/blooio/available?type=dedicated&area_code=415&area_code=628' \
 -H 'Authorization: Bearer bl_live_...'
Try it

Browsing (type=shared, or type=dedicated without an area code) returns masked rows. The full number is never exposed — only a masked national format, its area/country code, the ISO country, and a geocoded location (City, ST) when it can be resolved:

{
 "data": [
 {
 "masked_national": "(801) ***-****",
 "area_code": "801",
 "country_code": "1",
 "phone_number_country": "US",
 "location": "Salt Lake City, UT"
 }
 ],
 "has_more": false,
 "next_cursor": null
}

Passing area_code(s) for dedicated or inbound returns a quote: each code is either in stock (provisioned free) or requires a custom order (a custom area-code fee applies and a provisioning ticket is opened at purchase).

{
 "data": [
 { "area_code": "415", "matched": true },
 { "area_code": "628", "matched": false, "custom_order": true }
 ],
 "matched_count": 1,
 "custom_order_count": 1,
 "has_more": false,
 "next_cursor": null
}

Full schema: List available numbers.

2. Purchase numbers

Purchases are asynchronous and billable: the call charges your organization's default payment method and returns a purchase_id; the lines are provisioned once Stripe confirms the invoice.

Note An Idempotency-Key header is required. It doubles as the purchase_id and the poll handle, so replaying the same key returns the original purchase instead of charging again. Use a fresh key per distinct order and reuse it only on retries.

The plan field takes a specific plan id — not the tier name ("dedicated" alone returns invalid_plan):

plan id Tier Description
shared_nc shared Non-commercial Shared
shared_com shared Commercial Shared
dedicated_com dedicated Commercial Dedicated
dedicated_ent dedicated Enterprise Dedicated (volume-tiered pricing)
inbound_basic inbound Inbound Basic (reply-only)

dedicated plans accept quantity and area_codes; shared plans are auto-assigned from the pool and limited to one per organization.

curl -X POST 'https://api.blooio.com/v4/channels/blooio/purchases' \
 -H 'Authorization: Bearer bl_live_...' \
 -H 'Idempotency-Key: order-2026-08-07-abc123' \
 -H 'Content-Type: application/json' \
 -d '{
 "plan": "dedicated_com",
 "quantity": 2,
 "area_codes": ["415", "628"]
 }'
Try it

A successful call returns 202 Accepted:

{ "data": { "purchase_id": "idem_order-2026-08-07-abc123", "status": "provisioning" } }

Full schema: Purchase numbers.

Rules enforced

Enforced server-side, identically to the dashboard:

  • Payment method required — no saved card returns 402 no_payment_method.
  • One shared number per organization — a second shared purchase returns 409 shared_number_limit.
  • Per-order line cap — a single order can't exceed the cap (default 10 new lines); larger orders return 400 line_limit_exceeded. Apply to raise it from Limits → Number purchase limit in the dashboard.
  • Custom area codes — out-of-stock codes are billed the custom area-code fee and open a provisioning ticket; in-stock codes are assigned immediately at no custom fee.

Card authentication (3DS/SCA)

If the saved card needs authentication, the response is 202 with status: action_required and an action_url. Open the URL to complete authentication; provisioning continues afterward. Blooio also emits number.purchase.action_required and emails your org admins.

{
 "data": {
 "purchase_id": "idem_...",
 "status": "action_required",
 "action_url": "https://invoice.stripe.com/i/..."
 }
}

3. Track a purchase

Subscribe to the webhook events (recommended), or poll the purchase with its purchase_id.

curl 'https://api.blooio.com/v4/channels/blooio/purchases/idem_order-2026-08-07-abc123' \
 -H 'Authorization: Bearer bl_live_...'
Try it
{
 "data": {
 "purchase_id": "idem_...",
 "status": "completed",
 "allocations": [
 { "binding_id": "bind_...", "phone_number": "+14155550123", "type": "dedicated" }
 ]
 }
}

Full schema: Get purchase status.

4. Remove (unsubscribe) a number

Removing a line cancels its subscription — the same flow as the dashboard unsubscribe button (it updates your CRM and posts to Slack). Reference the line by its E.164 number or its ch_ channel id, and include one or more churn reasons.

curl -X DELETE 'https://api.blooio.com/v4/channels/+14155550123' \
 -H 'Authorization: Bearer bl_live_...' \
 -H 'Content-Type: application/json' \
 -d '{ "reasons": ["too_expensive", "no_longer_needed"] }'
Try it

A successful call returns 200 and emits a number.removed webhook. Full schema: Remove a number.

Webhook events

Because purchases complete asynchronously, subscribe to these events to learn the outcome:

Event When
number.purchase.completed Lines provisioned. Payload carries purchase_id, lines (each { phone_number, channel_id }), plus flat phone_numbers and channel_ids arrays covering every provisioned line.
number.purchase.action_required Card needs 3DS/SCA. Payload carries purchase_id, action_url.
number.purchase.failed Terminal failure; nothing provisioned. Payload carries purchase_id, reason.
number.removed A line was removed/unsubscribed. Payload carries phone_number, channel_id, binding_id, reasons.

For API-originated purchases, Blooio also emails your organization's admins the result (completed / action required / failed). Always verify the webhook signature before trusting a payload.