Number Purchase API
Programmatically browse, buy, and remove Blooio lines over the v4 API — including the KYC, access-approval, and billing steps required before you can purchase.
The Number Purchase API lets you provision Blooio lines from your own code instead of clicking through the dashboard. You can browse available inventory, buy shared, dedicated, or inbound lines, poll the purchase, and remove (unsubscribe) a line — all over the v4 API.
This is the same billing and provisioning flow the dashboard uses, so the two never drift. It's also how resellers and platforms (for example, AgentPhone) provision a fresh number for each of their end customers without a human in the loop.
Note
The Number Purchase API is a v4 feature. All endpoints live under
https://api.blooio.com/v4. Make sure the version toggle at the top of the docs is set to v4.
The full lifecycle — request access, browse, purchase, provision, track, and remove. Click a step for details; drag to pan, use the controls to zoom.
Before you can buy a line
Purchasing is gated. An organization has to clear three prerequisites before any purchase call will succeed — this keeps line provisioning tied to a verified, billable business.
1. Complete organization KYC
Open Organization Settings → Identity Verification in the dashboard and complete KYC with Stripe Identity. This verifies the business behind the organization and unlocks the features (including number purchasing) that aren't available to unverified organizations.
Until KYC shows as verified, requesting access is blocked and the API returns 403.
2. Request Number Purchase API access
In the same Organization Settings page, find the Number Purchase API section and submit the access request. You'll be asked for:
- What you're building — your use case.
- Website — your company/product URL.
- Notes — anything else the review team should know (optional).
Submitting opens a review ticket. A Blooio staff member approves it, which flips settings.number_purchase_api.enabled to true for your organization. Only an organization admin can submit the request.
Warning
Until access is approved, every Number Purchase API endpoint returns
403withcode: feature_not_enabledand the message "The number purchase API is not enabled for this organization. Request access in your organization settings."
3. Add a payment method
Number purchases are billable and charge your organization's default card on file. Add one in the dashboard's billing settings first. Without a saved card, POST /channels/blooio/purchases returns 402 with code: no_payment_method.
Per-order line cap
A single purchase can't exceed your organization's per-order line cap (default 10). If you need to buy more lines in one call, apply for an increase under Messaging → Limits in the dashboard. Exceeding the cap returns 400 with code: line_limit_exceeded.
Authentication
Both credential types work, exactly like the rest of the v4 API:
- API keys —
Authorization: Bearer bl_live_.... Full organization access. - OAuth app tokens — when calling on behalf of another organization, the token must carry the right scope:
numbers:read— list available inventory and read purchase status.numbers:manage— purchase and remove lines.
Authorization: Bearer YOUR_BLOOIO_API_KEYSee Calling the API with OAuth tokens for the app flow.
Step 1 — Browse available inventory
Use GET /channels/blooio/available to see what's in stock. The type query selects the plan family:
shared— available shared lines (returns masked inventory rows).dedicated— unassigned dedicated inventory.inbound— reply-only inbound lines (provisioned by area code — passarea_codeto get a quote).
curl 'https://api.blooio.com/v4/channels/blooio/available?type=dedicated&country=US&limit=5' \
-H 'Authorization: Bearer YOUR_BLOOIO_API_KEY'Try it{
"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
}Note
The full phone number is never returned before purchase — only a masked form (
(801) ***-****). The actual line is revealed once it's provisioned to your account.
Full schema: List available numbers.
Area-code quotes
For dedicated or inbound, pass one or more area_code values to check availability by area code. Each requested code comes back as either in stock (provisioned free) or needing a custom order (a custom area-code fee applies and a provisioning ticket is opened at purchase).
curl 'https://api.blooio.com/v4/channels/blooio/available?type=dedicated&area_code=801&area_code=212' \
-H 'Authorization: Bearer YOUR_BLOOIO_API_KEY'Try it{
"data": [
{ "area_code": "801", "matched": true },
{ "area_code": "212", "matched": false, "custom_order": true }
],
"matched_count": 1,
"custom_order_count": 1,
"has_more": false,
"next_cursor": null
}Step 2 — Purchase a line
Buy lines with POST /channels/blooio/purchases. The call charges your default payment method and returns a purchase_id; the lines are provisioned asynchronously once Stripe confirms the invoice.
Warning
An
Idempotency-Keyheader is required on every purchase. It makes the billable call safe to retry — replaying the same key returns the same purchase instead of charging twice.
curl -X POST 'https://api.blooio.com/v4/channels/blooio/purchases' \
-H 'Authorization: Bearer YOUR_BLOOIO_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3c1e0a-9b2d-4c11-8f6a-2b1c9d4e5f60' \
-d '{
"plan": "dedicated_com",
"quantity": 2,
"area_codes": ["415", "628"]
}'Try it| Field | Type | Notes |
|---|---|---|
plan |
string (required) | Plan id — use the specific id (e.g. dedicated_com), not the family name (dedicated returns invalid_plan). See Plans. |
quantity |
integer | Number of new lines (dedicated/inbound). Shared is always 1. Defaults to 1. |
area_codes |
string[] | Preferred 3-digit US area codes. Out-of-stock codes incur a custom fee and open a ticket. |
zip_codes |
string[] | Preferred ZIP codes, used when an area code isn't specified. |
A successful call returns 202 Accepted while provisioning proceeds in the background:
{ "data": { "purchase_id": "idem_abc123", "status": "provisioning" } }Full schema: Purchase numbers.
Rules enforced
Checked server-side before the card is charged, 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. Raise it from Messaging → Limits 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.
Cards that need authentication (3DS/SCA)
If the card requires 3DS/SCA, you get 202 with status: action_required and an action_url. Send the customer to that URL to authenticate; a number.purchase.action_required webhook also fires.
{
"data": {
"purchase_id": "idem_abc123",
"status": "action_required",
"action_url": "https://invoice.stripe.com/i/..."
}
}Step 3 — Track provisioning
Provisioning finishes after the invoice is paid. You can either poll or listen for a webhook.
Poll the purchase
curl 'https://api.blooio.com/v4/channels/blooio/purchases/idem_abc123' \
-H 'Authorization: Bearer YOUR_BLOOIO_API_KEY'Try itThe status moves from pending/provisioning to completed (with the provisioned allocations) or failed.
{
"data": {
"purchase_id": "idem_abc123",
"status": "completed",
"allocations": [
{ "binding_id": "bind_...", "phone_number": "+14155550142", "type": "dedicated" }
]
}
}Full schema: Get purchase status.
Or listen for webhooks
Prefer webhooks over polling for production. 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. Always verify the webhook signature before trusting a payload.
Removing a line
Unsubscribe an owned line with DELETE /channels/{channel}. The {channel} path segment accepts either the E.164 number or the ch_ channel id. A reasons array is required — it's the same churn-reason handling as the dashboard unsubscribe button, and it emits a number.removed webhook.
curl -X DELETE 'https://api.blooio.com/v4/channels/%2B14155550142' \
-H 'Authorization: Bearer YOUR_BLOOIO_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "reasons": ["too_expensive", "no_longer_needed"] }'Try it{
"data": {
"phone_number": "+14155550142",
"channel_id": "ch_...",
"reasons": ["Too expensive", "No longer needed"]
}
}Full schema: Remove a number.
Plans
Pass the specific plan id when purchasing. Pricing shown is indicative — see the pricing page for current rates.
| Plan id | Family | Description |
|---|---|---|
shared_nc |
shared | Entry shared line (limited new conversations/day). One per organization. |
shared_com |
shared | Commercial shared line (higher new-conversation allowance). One per organization. |
dedicated_com |
dedicated | Dedicated commercial line. Buy several with quantity. |
dedicated_ent |
dedicated | Dedicated enterprise line with tiered volume pricing. |
inbound_basic |
inbound | Reply-only inbound line — outbound is gated by prior inbound history (see Number pools). |
Note
Shared plans are limited to a single line per organization. Sending
quantity> 1 on a shared plan returns409withcode: shared_number_limit.
Errors
| Status | code |
Meaning |
|---|---|---|
403 |
feature_not_enabled |
Number Purchase API access hasn't been approved for the organization. |
402 |
no_payment_method |
No saved card. Add one in billing before purchasing. |
402 |
payment_failed |
The card was declined; nothing was provisioned. |
400 |
idempotency_key_required |
Purchases require an Idempotency-Key header. |
400 |
invalid_plan |
Unknown plan id (e.g. dedicated instead of dedicated_com). |
400 |
plan_sunset |
The plan is no longer available for new purchases. |
400 |
line_limit_exceeded |
Order exceeds your per-order line cap. Apply for an increase under Messaging → Limits. |
409 |
shared_number_limit |
A shared plan is limited to one line per organization. |
Related
- Number provisioning — the same lifecycle as a core concept reference.
- Number pools — how a sender number is selected once you own lines.
- Webhooks and verifying signatures — subscribe to
number.purchase.*andnumber.removedevents. - Calling the API with OAuth tokens — provisioning lines on behalf of another organization.
- Reference: List available · Purchase · Get status · Remove.
- AgentPhone case study — provisioning a line per end customer at scale.