Blooio API Reference

Number Pools

How sender number selection works for Blooio and Twilio messages

A number pool is a collection of phone numbers assigned to your API key. When you send a message, Blooio selects a sender number from this pool—either automatically or based on your specification.

How number pools work

Each API key in Blooio has its own number pool. The pool determines which phone numbers can be used as the sender (from_number) when sending messages through that API key.

┌─────────────────────────────────────────────┐
│              Your Organization              │
├─────────────────────────────────────────────┤
│                                             │
│  ┌─────────────────┐  ┌─────────────────┐   │
│  │   API Key A     │  │   API Key B     │   │
│  │   (iMessage)    │  │   (Twilio)      │   │
│  ├─────────────────┤  ├─────────────────┤   │
│  │ +1 555-111-0001 │  │ +1 555-222-0001 │   │
│  │ +1 555-111-0002 │  │ +1 555-222-0002 │   │
│  │ +1 555-111-0003 │  │ +1 555-222-0003 │   │
│  └─────────────────┘  └─────────────────┘   │
│                                             │
└─────────────────────────────────────────────┘

Each API key can only be assigned either Blooio (iMessage) numbers or Twilio numbers—never both. This ensures clear separation between messaging protocols.

Blooio (iMessage) number pools

Automatic rotation

For Blooio API keys, number selection is fully automatic with intelligent rotation:

  1. New conversations: Blooio selects an available number from your pool
  2. Ongoing conversations: The same number is used to maintain consistency
  3. Load distribution: Numbers rotate to distribute traffic evenly
# from_number is optional for Blooio - auto-selected from pool
curl -X POST 'https://backend.blooio.com/v2/api/chats/%2B15551234567/messages' \
  -H 'Authorization: Bearer YOUR_BLOOIO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello!"}'

Conversation stickiness

Blooio remembers which number was used for each contact. When you message someone again, Blooio automatically uses the same sender number—no tracking required on your end.

This creates a natural experience where contacts always see messages from the same number, as if they're texting a consistent person.

Explicit number selection

You can override automatic selection by specifying from_number:

curl -X POST 'https://backend.blooio.com/v2/api/chats/%2B15551234567/messages' \
  -H 'Authorization: Bearer YOUR_BLOOIO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Hello!",
    "from_number": "+15551110002"
  }'

The specified from_number must be in your API key's pool. Attempting to use a number from a different API key results in a 403 Forbidden error.

Allocation pools

In the Blooio dashboard, you can configure allocation pools to control how numbers are distributed:

  • Round-robin: Cycle through numbers evenly
  • Weighted: Assign higher volume to specific numbers
  • Geographic: Match sender area codes to recipient regions

These settings are configured per-organization in the dashboard.

Twilio number pools

Manual or first-available selection

Twilio number pools work differently. When you omit from_number:

  1. Blooio selects the first number in your API key's Twilio pool
  2. No automatic rotation or conversation tracking occurs
# from_number omitted - uses first assigned Twilio number
curl -X POST 'https://backend.blooio.com/v2/api/chats/%2B15551234567/messages' \
  -H 'Authorization: Bearer YOUR_TWILIO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello via SMS!"}'

No automatic stickiness

Unlike Blooio, Twilio pools don't maintain conversation stickiness. If you need the same number for ongoing conversations, track it yourself:

// Store which Twilio number was used for each contact
const fromNumbers = new Map()

async function sendTwilioMessage(to, text) {
  // Check if we've messaged this contact before
  let fromNumber = fromNumbers.get(to)
  
  if (!fromNumber) {
    // First message - let Blooio auto-select, then store it
    const res = await sendMessage(to, { text })
    fromNumber = res.from_number  // Returned in response
    fromNumbers.set(to, fromNumber)
    return res
  }
  
  // Subsequent messages - use same number
  return sendMessage(to, { text, from_number: fromNumber })
}

For Twilio, explicitly specifying from_number is recommended for production:

curl -X POST 'https://backend.blooio.com/v2/api/chats/%2B15551234567/messages' \
  -H 'Authorization: Bearer YOUR_TWILIO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Hello via SMS!",
    "from_number": "+15552220001"
  }'

Inbound numbers (reply-only)

Numbers on the Inbound plan are reply-only and behave a little differently from a normal Blooio number:

  • They cannot start new conversations. Sending to a contact (or a group) that has never messaged the number returns 403 inbound_only_no_prior_inbound (see Errors).
  • They can reply to any contact that has messaged them at least once. Once an inbound message exists for an (allocation, recipient) pair, outbound from that pair is allowed.
  • Group chats work too. If someone adds your Inbound number to a group and a participant texts the group, your Inbound number can reply in that group. The "recipient" key for groups is the group_id, so the reply-only check is (allocation, group_id) for groups and (allocation, contact) for 1:1.
  • Multiple inbound numbers can live on the same Mac, distinguished by an internal line slot. From the API's perspective, each inbound number is its own E.164 like any other Blooio number.

When an API key has both Inbound and Dedicated/Shared numbers, you can mix:

// Dedicated number — fine for the first outreach.
await sendMessage(contact, { text: "Hi! Just following up.", from_number: "+15551110001" })

// Once they reply, your Inbound AI agent can take over the thread.
await sendMessage(contact, { text: "Got it — let me check on that.", from_number: "+15559990001" })

/me/numbers returns a type field ("shared" | "dedicated" | "inbound" | "trial" | "2fa") so you can route outbound through the right number deterministically.

The reply-only constraint is enforced server-side per (allocation, recipient) — where recipient is the contact identifier for 1:1 chats and the group_id for group chats. There's no "warm up" period — the very first outbound from an Inbound number to a contact (or to a group) succeeds the moment we record an inbound message from that contact / in that group.

Comparison: Blooio vs Twilio pools

FeatureBlooio (iMessage)Twilio (SMS)
Auto-selectionIntelligent rotationFirst available
Conversation stickinessAutomaticManual tracking required
Pool configurationDashboard allocation poolsAssign numbers to API key
ProtocoliMessageSMS/MMS
Multi-protocol supportNo (iMessage only)No (Twilio only per key)

Setting up your number pool

For Blooio numbers

  1. Go to Numbers in the dashboard
  2. Your Blooio-registered numbers appear automatically
  3. Create an API key and assign numbers to it
  4. Configure allocation preferences in Settings

For Twilio numbers

  1. Go to IntegrationsTwilioConnect
  2. Enter your Twilio Account SID, API Key SID, and API Key Secret
  3. Select which Twilio numbers to import
  4. Create a new API key (separate from your Blooio key)
  5. Assign imported Twilio numbers to the API key

See Twilio Integration for detailed setup instructions.

Best practices

1. Separate API keys by use case

Create distinct API keys for different purposes:

┌─────────────────────────────────────────────┐
│  API Key: "Sales Team"    → Pool: 5 numbers │
│  API Key: "Support Team"  → Pool: 3 numbers │
│  API Key: "Marketing SMS" → Pool: 10 Twilio │
└─────────────────────────────────────────────┘

2. Don't mix protocols in logic

Keep Blooio (iMessage) and Twilio (SMS) flows separate in your application. Different keys, different pools, different behavior.

3. Monitor number health

Numbers can become flagged by carriers. Monitor delivery rates per number and rotate out underperforming numbers.

4. Use geographic matching when possible

For Twilio pools, consider matching sender area codes to recipient regions:

function selectFromNumber(recipientNumber, twilioPool) {
  const recipientAreaCode = recipientNumber.slice(2, 5)
  
  // Find a number with matching area code
  const match = twilioPool.find(n => n.slice(2, 5) === recipientAreaCode)
  return match || twilioPool[0]  // Fall back to first number
}

5. Plan for growth

If you anticipate high volume, provision more numbers early. Adding numbers to a pool is easy; dealing with deliverability issues from over-used numbers is not.

Common errors

"This API key does not own the specified from_number"

The from_number you specified isn't assigned to your API key's pool.

Fix: Either remove from_number to use auto-selection, or assign the number to your API key in the dashboard.

"No numbers assigned to this API key"

Your API key has an empty pool.

Fix: Go to the Numbers page and assign at least one number to your API key.

"API key already owns Blooio numbers"

You tried to assign Twilio numbers to an API key that already has Blooio numbers.

Fix: Create a separate API key for Twilio numbers. Each key can only hold one protocol type.

On this page