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 403 with code: feature_not_enabled and 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. They're paid from your organization's account credit first, then its default card on file. Add a card in the dashboard's billing settings unless your credit covers your purchases. When the credit doesn't cover a purchase and there's no saved card, POST /channels/blooio/purchases returns 402 with code: no_payment_method and charges nothing.

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 Overview → 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_KEY

See 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 — pass area_code to 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 account credit, then your default payment method, and returns a purchase_id; the lines are provisioned asynchronously once Stripe confirms the invoice.

Warning

An Idempotency-Key header 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.
custom_requests (object | null)[] Per-line requests for a line Blooio staff set up by hand, like your own eSIM, a port-in, or a number from another country. Each custom line costs the one-time custom fee. See Custom provisioning requests.
auto_bind string The api_key_id of an API key in your organization. Every line in the purchase is assigned to that key once it has a number. See Assign the lines to an API key.

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:

  • Credit or a payment method required — without a saved card, a purchase your credit doesn't cover in full 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 Overview → 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.
  • Custom requests — each line with a custom request is billed the same custom fee and opens a provisioning ticket. A line is charged the fee once, even if it also asks for an out-of-stock area code.
  • auto_bind — the key must be a live key in your organization, and on plans without number pools it can own only one line. See Assign the lines to an API key.

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/..."
  }
}

Custom provisioning requests

Some lines can't come from our stock. Examples include running iMessage on an eSIM you already own, porting in a number from your carrier, a number from another country, or any other setup that needs a person. For these, send custom_requests with the purchase. Each line with a custom request is set up by Blooio staff with the same provisioning process we use for custom area codes.

Request shape

custom_requests is an array matched to the lines in your order by position, the same way area_codes is. Entry 0 describes line 1, entry 1 describes line 2, and so on.

Entry Meaning
{ "notes": "..." } This line is a custom request. notes is free text telling our staff what you need.
{} This line is a custom request with no notes. Staff will follow up on the ticket if they need details.
null, or no entry This line is fulfilled normally: from stock, or by its area code or ZIP.

notes is passed to our staff exactly as you send it. We don't parse or validate it, so write it for a person and include everything they need. The only checks are on the shape:

  • custom_requests must be an array of objects or null.
  • It can't have more entries than quantity.
  • It's only accepted on dedicated and inbound plans. Shared lines come from a shared pool and can't be customized.

Examples

One line that runs on an eSIM you own:

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: esim-move-8f2c' \
  -d '{
    "plan": "dedicated_com",
    "quantity": 1,
    "custom_requests": [
      { "notes": "Install our eSIM for +15551234567 (T-Mobile). Activation code: LPA:1$smdp.example.com$K2-ABC123. ICCID 8901260123456789012." }
    ]
  }'
Try it

A mixed order. Line 1 ports in an existing number, line 2 is a normal in-stock line in area code 415, and line 3 needs a UK number:

{
  "plan": "dedicated_com",
  "quantity": 3,
  "area_codes": ["", "415", ""],
  "custom_requests": [
    { "notes": "Port in +12125550123 from Verizon. Account 123456789, transfer PIN 4821, billing ZIP 10001." },
    null,
    { "notes": "UK mobile number (+44), London if possible." }
  ]
}

What happens after you buy

  1. You're charged up front. The call charges the plan price for every line, plus the one-time custom fee ($75) for each custom line. It returns 202 with status: provisioning, like any purchase.
  2. Each custom line opens a ticket. Once payment clears, every custom line becomes a pending line with its own provisioning ticket. The ticket's first message repeats your notes. A custom line is never assigned automatically, even when matching stock exists.
  3. Our staff set it up. The team reads the notes and does the work, such as installing the eSIM or completing the port. Then they assign the line to your organization, which closes its ticket.
  4. The line goes live. It gets its phone number, and you can send from it.

Turnaround depends on the request. A port-in, for example, waits on the losing carrier.

Following a custom line

  • API: poll the purchase. Each pending custom line appears in allocations with phone_number: null and a ticket_id. Read that ticket with GET /v4/tickets/{ticketId}. The purchase stays pending until every line has a number, then becomes completed.
  • Dashboard: the line shows under Pending provisioning on the Blooio P2P page and as Provisioning… on the Billing page. Its View request link opens the ticket with your notes and its status.

Warning

number.purchase.completed fires when payment clears, not when staff finish a custom line. Custom lines aren't listed in that event's lines or phone_numbers, and no second webhook fires when staff assign the line later. Poll GET /channels/blooio/purchases/{purchaseId} or watch the ticket until the line has a phone_number.

Note

Your notes are stored as sent. Blooio staff can see them, and so can anyone in your organization who can view tickets. Only include credentials, such as an eSIM activation code or a port-out PIN, if you're comfortable with that. If you'd rather not include them, say so in the notes, and staff will follow up on the ticket. You can't reply on purchase tickets.

Assign the lines to an API key

A new line has no owner, so a send authenticated with an API key can't use it until you assign it to the key. To skip that step, name the key in the purchase with auto_bind, using the key's api_key_id. It's a public identifier, so you never send a key's secret to name it. Copy it from the key's ⋯ menu in the dashboard, or, with an app token, read it from GET /v4/api-keys.

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: acme-lines-01' \
  -d '{
    "plan": "dedicated_ent",
    "quantity": 2,
    "area_codes": ["415", "212"],
    "auto_bind": "acme-prod"
  }'
Try it

Each line is assigned to the key as soon as it gets its number:

  • In-stock lines are assigned when payment clears.
  • Lines Blooio staff provision (custom requests, ZIP codes, out-of-stock area codes) are assigned when staff finish them, which can be days later. You don't need to do anything when they go live.
  • A key deprecated before its line is provisioned doesn't get the line, which is then left unassigned.

A line is assigned once. If Blooio later swaps the number behind it, for example after a carrier ban, the replacement carries over the line's current owners, and auto_bind doesn't assign it again. A line you've moved to another key stays there.

Both API keys and app tokens can send auto_bind, and it can name any live key in your organization. Without it, the lines are bought unassigned, as before.

Note

Plans without number pools (shared_nc, shared_com, dedicated_com) let a key own one line on such a plan. On those plans, auto_bind needs quantity: 1 and a key that doesn't already own a line on one of them. Otherwise the purchase returns 409 auto_bind_exceeds_plan before anything is charged.

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 it

The status moves from pending to completed, once every line in allocations has a phone number, or to failed.

{
  "data": {
    "purchase_id": "idem_abc123",
    "status": "completed",
    "allocations": [
      { "binding_id": "bind_...", "phone_number": "+14155550142", "type": "dedicated", "ticket_id": null }
    ]
  }
}

Some lines are set up by Blooio staff: custom requests, and area codes or ZIPs that aren't in stock. While staff work on one, it has phone_number: null and the ticket_id of its provisioning ticket, and the purchase stays pending:

{
  "data": {
    "purchase_id": "idem_abc123",
    "status": "pending",
    "allocations": [
      { "binding_id": "bind_...", "phone_number": "+14155550142", "type": "dedicated", "ticket_id": null },
      { "binding_id": "bind_...", "phone_number": null, "type": null, "ticket_id": "tkt_..." }
    ]
  }
}

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 Payment cleared and lines provisioned. Payload carries purchase_id, lines (each { phone_number, channel_id }), plus flat phone_numbers and channel_ids arrays covering every line assigned so far. Lines still waiting on staff (custom requests, out-of-stock area codes) aren't included yet. Poll the purchase for those.
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 returns 409 with code: shared_number_limit.

Note

dedicated and inbound plans charge a one-time $75 custom fee for each line Blooio staff set up by hand. That covers every line with a custom request and every requested area code or ZIP that isn't in stock. It appears on the invoice as the custom area code fee.

Errors

Status code Meaning
403 feature_not_enabled Number Purchase API access hasn't been approved for the organization.
402 no_payment_method Your credit doesn't cover the purchase and there's no saved card. Add one in billing.
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 Overview → Limits.
400 invalid_custom_requests custom_requests isn't an array, or an entry isn't an object or null.
400 custom_requests_exceed_quantity custom_requests has more entries than the order has lines (quantity).
400 custom_requests_not_supported A custom request was sent on a shared plan. Use a dedicated or inbound plan.
400 invalid_auto_bind auto_bind isn't a non-empty string.
404 auto_bind_api_key_not_found No live API key in your organization has that api_key_id.
409 shared_number_limit A shared plan is limited to one line per organization.
409 auto_bind_exceeds_plan The plan has no number pools, and the order has more than one line or the key already owns a line on such a plan.