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:
- Verify your organization's KYC (required first).
- An admin opens Settings → Number Purchase API and submits the request (what you're building and your website). This opens an internal ticket.
- A Blooio admin reviews and approves. Your org's
numbers:read/numbers:manageendpoints then start responding.
Authenticate
Both credential types work:
- API keys —
Authorization: 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 itBrowsing (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-Keyheader is required. It doubles as thepurchase_idand 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 itA 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 itA 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.