Blooio RCS

Message people in Google Messages from a verified business sender with your name, logo, and colors, and send rich cards, carousels, and tappable suggestions through the same v4 messages API

Blooio RCS sends RCS business messages. They arrive from a verified sender that shows your brand's name, logo, and colors instead of a phone number, and they can carry rich cards, carousels, and suggestion chips that people tap to reply or act. Each sender is an rcs_business channel: you send from it with POST /v4/messages like any other channel, and you can put it ahead of SMS in a priority so contacts whose phones get RCS receive it and everyone else gets a text.

Note

Messages from an RCS sender carry protocol: "rcs_business". protocol: "rcs" is something else: carrier RCS on a Blooio iMessage line, which has no verified sender.

How a sender goes live

Step Endpoint What happens
1. Register the brand PUT /v4/rcs/brand Google reviews the business behind your senders.
2. Upload the profile images POST /v4/rcs/assets Blooio hosts the logo and banner and returns their URLs.
3. Create a sender POST /v4/rcs/senders A draft sender and its rcs_business channel, which stays pending_verification for now.
4. Submit the sender POST /v4/rcs/senders/{channelId}/submit Google verifies the sender. Once it's verified, its channel is active.
5. Invite test devices POST /v4/rcs/senders/{channelId}/test_devices Testers accept the invite in Google Messages, and a verified sender can message them.
6. Request the launch POST /v4/rcs/senders/{channelId}/launch US carriers review how people opt in. Once they approve, the sender is live and can message anyone whose phone gets RCS.

Google and the carriers review on their own schedules. Watch the registration.rcs_brand.updated and registration.rcs_sender.updated webhooks, or re-read the brand and sender, which checks the review for news while it's open. You can do every step from the dashboard instead, under Business Channels → RCS.

1. Register the brand

The brand is the business people see behind every sender. An organization has one.

curl -X PUT 'https://api.blooio.com/v4/rcs/brand' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "display_name": "Acme",
    "legal_name": "Acme Inc.",
    "legal_entity_type": "CORPORATION",
    "organization_type": "PRIVATE_PROFIT",
    "website": "https://acme.com",
    "ein": "12-3456789",
    "street": "1 Main St",
    "city": "Austin",
    "state": "TX",
    "postal_code": "78701",
    "country": "US",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ops@acme.com",
    "phone": "+15125550100"
  }'
Try it
  • legal_entity_type is LIMITED_LIABILITY_COMPANY, CORPORATION, S_CORPORATION, PARTNERSHIP, or SOLE_PROPRIETORSHIP.
  • organization_type is PRIVATE_PROFIT, PUBLIC_PROFIT, NON_PROFIT, or GOVERNMENT. A public company also sends stock_symbol as EXCHANGE:SYMBOL.
  • street2 and title (the contact person's) are optional.

The brand moves from submitting to in_review to verified. Once it's submitted it can't change (409 rcs_brand_locked), except its point of contact, by change request. A rejected or failed brand can be registered again with corrected details.

2. Upload the profile images

Upload each image as multipart/form-data with its kind, and use the returned url in the sender's profile.

curl -X POST 'https://api.blooio.com/v4/rcs/assets' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -F 'kind=logo' \
  -F 'file=@logo.png'
Try it
kind Used as Limits
logo profile.logo_url PNG or JPEG, exactly 224×224, up to 50 KB
banner profile.banner_url PNG or JPEG, exactly 1440×448, up to 200 KB
opt_in_media call_to_action_media_url at launch PNG or JPEG screenshot, up to 5 MB
test_video test_video_url at launch MP4 or QuickTime, up to 50 MB

3. Create a sender

A sender is a branded profile people message. Create one per purpose, such as order updates and support.

curl -X POST 'https://api.blooio.com/v4/rcs/senders' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "use_case": "TRANSACTIONAL",
    "alias": "acme-orders",
    "profile": {
      "display_name": "Acme Orders",
      "description": "Order and delivery updates from Acme.",
      "logo_url": "https://cdn.blooio.com/rcs-assets/.../logo.png",
      "banner_url": "https://cdn.blooio.com/rcs-assets/.../banner.png",
      "brand_color": "#1A73E8",
      "privacy_policy_url": "https://acme.com/privacy",
      "terms_url": "https://acme.com/terms",
      "website": { "url": "https://acme.com", "label": "Shop" },
      "phone": { "number": "+15125550100", "label": "Support" }
    }
  }'
Try it
  • use_case is TRANSACTIONAL, PROMOTIONAL, OTP, or MULTI_USE.
  • display_name is up to 40 characters and description up to 100. brand_color is a hex color.
  • The profile needs at least one of phone, email, and website, each with a label of up to 25 characters.
  • alias is optional; without one, Blooio makes one from the display name. Send from either the alias or the channel_id.

The response is the sender, with its channel_id. Change a draft with PATCH /v4/rcs/senders/{channelId}. Once it's submitted, only its name, logo, and banner change, by change request.

4. Submit the sender

curl -X POST 'https://api.blooio.com/v4/rcs/senders/ch_.../submit' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Try it

The brand must be verified first (409 rcs_brand_not_verified). A retry after a lost response finds the same submission. The sender moves through submitted and verifying to verified, and its channel becomes active.

Submitting starts the sender's monthly fee, which includes its yearly verification, so the organization needs a card on file (402 no_payment_method); a declined card returns 402 payment_failed and submits nothing. If Google turns the submission away before reviewing it, the sender returns to draft and its month is credited back.

5. Invite test devices

Before launch, a sender can only message phones that accepted its test invite.

curl -X POST 'https://api.blooio.com/v4/rcs/senders/ch_.../test_devices' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "phone_number": "+15125550100" }'
Try it

The tester gets a prompt in Google Messages. Their device's status is pending until they accept, then accepted. Remove a tester with DELETE /v4/rcs/senders/{channelId}/test_devices/{testDeviceId}.

6. Request the launch

Carriers decide whether the sender may message anyone, based on what it sends and how people agree to get it. The launch request can be sent once, after the sender is submitted.

curl -X POST 'https://api.blooio.com/v4/rcs/senders/ch_.../launch' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "company_overview": "Acme sells outdoor gear online.",
    "agent_overview": "Sends order confirmations and delivery updates, and answers order questions.",
    "interactions": ["TRANSACTIONAL_UPDATES", "CUSTOMER_SUPPORT"],
    "message_examples": [
      "Your order #1234 is confirmed. We will text you when it ships.",
      "Order #1234 shipped. Track it here: https://acme.com/t/1234",
      "Order #1234 was delivered. Reply HELP for help or STOP to opt out."
    ],
    "opt_in_methods": ["WEBSITE"],
    "call_to_action": "Get order updates in Messages",
    "call_to_action_url": "https://acme.com/checkout",
    "call_to_action_media_url": "https://cdn.blooio.com/rcs-assets/.../opt-in.png",
    "double_opt_in": false,
    "opt_in_message": "You are subscribed to Acme order updates. Reply STOP to opt out.",
    "help_response": "Acme order updates. Visit https://acme.com/help or reply STOP to opt out.",
    "opt_out_response": "You are unsubscribed from Acme order updates. Reply START to resubscribe.",
    "test_video_url": "https://cdn.blooio.com/rcs-assets/.../test.mp4"
  }'
Try it
  • interactions lists what the sender does: TRANSACTIONAL_UPDATES, CUSTOMER_SUPPORT, ACCOUNT_ALERTS, TWO_WAY_CONVERSATION, MARKETING_OR_PROMOTIONAL, LOYALTY_OR_REWARD.
  • message_examples holds 3 to 10 messages as recipients will see them.
  • opt_in_methods is any of WEBSITE, MOBILE_APP, SMS, QR_CODE, SALE_POINT. WEBSITE needs call_to_action_url, and WEBSITE or MOBILE_APP needs call_to_action_media_url, a screenshot of where people opt in.
  • With double_opt_in: true, send the confirmation request as double_opt_in_message.
  • help_response, opt_out_response, and opt_in_message are also what Blooio replies to HELP, STOP, and START (see Opt-out and help).
  • test_video_url is a recording of the sender in use; test_notes is optional.

Each carrier's answer arrives in carrier_approvals. The sender is live once they approve. The launch needs a card on file (402 no_payment_method); its one-time fee is billed when T-Mobile approves.

Sender statuses

Status Who it can message
draft Nobody. Still editable.
submitting, submitted, verifying Nobody yet: Google is verifying it.
verified Its accepted test devices. Its channel is active.
launching, launched Its accepted test devices, while the carriers review the launch.
live Anyone whose phone gets RCS.
rejected, failed Nobody. Contact support to resubmit.

Changing or removing a sender

Google and the carriers take changes to a submitted sender or brand by hand, so each one is a paid change request that Blooio carries to them.

Change Endpoint Fields Reviews billed
A submitted sender's profile POST /v4/rcs/senders/{channelId}/changes Any of display_name, logo_url, banner_url One per field changed
The brand's point of contact POST /v4/rcs/brand/changes Any of first_name, last_name, title, email, phone One per request
curl -X POST 'https://api.blooio.com/v4/rcs/senders/ch_.../changes' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "display_name": "Acme Shop", "logo_url": "https://cdn.blooio.com/rcs-assets/.../logo.png" }'
Try it
  • Send only the fields that change. Any other field returns 400 invalid_field, and a request that changes nothing returns 400 no_changes.
  • The brand takes contact changes once it's in_review or verified (409 rcs_brand_state_conflict). A draft sender is edited with PATCH instead (409 rcs_sender_state_conflict).
  • A sender or brand has one open request at a time (409 rcs_change_in_progress).
  • Its reviews are charged when you make the request, so the same 402 errors as submitting apply.

The response is the change request, 201 with a change_id (rcschg_...) and the number of reviews it's billed for. Its status moves from requested to sent once Blooio has passed it on, then to applied, when the sender or brand shows the change, or rejected, with the reason in note. List them with GET /v4/rcs/changes. Until a request is sent, POST /v4/rcs/changes/{changeId}/cancel withdraws it and refunds its reviews; a request rejected before it was sent is refunded too.

To stop using a sender, remove it with DELETE /v4/rcs/senders/{channelId}. Its channel stops sending and receiving for good, and its monthly fee stops, with the rest of the month credited. A sender Google has is then deleted there too, which shows in GET /v4/rcs/changes as a sender_removal request. A sender still being submitted can't be removed yet (409 rcs_sender_submission_in_progress). A rejected or failed sender keeps its monthly fee until it's removed.

Sending

Send with POST /v4/messages and set from to the sender's channel_id or alias. Recipients are phone numbers in E.164; RCS senders don't message groups.

Text with suggestions

suggestions adds up to 11 chips under a text, attachments, or rich_card message.

cURL

curl -X POST 'https://api.blooio.com/v4/messages' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "acme-orders",
    "to": "+15551234567",
    "text": "Your order #1234 shipped and arrives Thursday.",
    "suggestions": [
      { "type": "open_url", "text": "Track order", "url": "https://acme.com/t/1234" },
      { "type": "reply", "text": "Change address", "id": "change_address" },
      { "type": "dial", "text": "Call support", "phone_number": "+15125550100" }
    ]
  }'
Try it

Node.js

await fetch('https://api.blooio.com/v4/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.BLOOIO_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    from: 'acme-orders',
    to: '+15551234567',
    text: 'Your order #1234 shipped and arrives Thursday.',
    suggestions: [
      { type: 'open_url', text: 'Track order', url: 'https://acme.com/t/1234' },
      { type: 'reply', text: 'Change address', id: 'change_address' },
      { type: 'dial', text: 'Call support', phone_number: '+15125550100' }
    ]
  })
})

Python


requests.post('https://api.blooio.com/v4/messages',
  headers={
    'Authorization': f"Bearer {os.environ['BLOOIO_API_KEY']}",
    'Content-Type': 'application/json'
  },
  json={
    'from': 'acme-orders',
    'to': '+15551234567',
    'text': 'Your order #1234 shipped and arrives Thursday.',
    'suggestions': [
      { 'type': 'open_url', 'text': 'Track order', 'url': 'https://acme.com/t/1234' },
      { 'type': 'reply', 'text': 'Change address', 'id': 'change_address' },
      { 'type': 'dial', 'text': 'Call support', 'phone_number': '+15125550100' }
    ]
  }
)

Every suggestion has a type and a text label of up to 25 characters. id (up to 2,048 characters) comes back when the chip is tapped and defaults to text.

type Does Fields
reply Sends its text back as a reply none
open_url Opens a web page url (http or https)
dial Starts a call phone_number (E.164)
view_location Shows a place on a map latitude and longitude, or query; optional label
share_location Asks for the person's location none
create_calendar_event Adds an event to their calendar title, start_time, end_time (ISO 8601); optional description

Rich cards and carousels

rich_card sends one card, or a swipeable carousel of up to 10. Each card needs a title, description, or media, and can carry up to 4 suggestions of its own.

curl -X POST 'https://api.blooio.com/v4/messages' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "acme-orders",
    "to": "+15551234567",
    "rich_card": {
      "cards": [
        {
          "title": "Trail Runner 2",
          "description": "Back in stock in your size.",
          "media": { "url": "https://acme.com/img/trail-runner.jpg", "height": "medium" },
          "suggestions": [{ "type": "open_url", "text": "Buy now", "url": "https://acme.com/p/trail-runner" }]
        },
        {
          "title": "Summit Jacket",
          "description": "20% off this week.",
          "media": { "url": "https://acme.com/img/summit.jpg", "height": "medium" },
          "suggestions": [{ "type": "reply", "text": "Tell me more", "id": "summit_jacket" }]
        }
      ]
    }
  }'
Try it

A card's title is up to 200 characters and its description up to 2,000. media.height is short, medium, or tall.

Quick replies

An interactive quick reply, in the same shape Apple Messages for Business uses, sends summary_text with each item as a reply chip.

curl -X POST 'https://api.blooio.com/v4/messages' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "acme-orders",
    "to": "+15551234567",
    "interactive": {
      "kind": "quick_reply",
      "summary_text": "When should we deliver?",
      "items": [
        { "id": "morning", "title": "Morning" },
        { "id": "afternoon", "title": "Afternoon" }
      ]
    }
  }'
Try it

RCS supports only the quick_reply kind (other kinds return 422 interactive_kind_unsupported_for_channel_type), with up to 11 items whose titles are up to 25 characters.

Text and media limits

Content Limit
text 3,072 characters
attachments One per message; its text caption up to 2,000 characters
suggestions 11 per message, 4 per card
rich_card.cards 10 per message
Files 5 MB per message, counting the attachment or every card's media

Files go by URL, and Google downloads them from there for the phone, so each URL must be public. Blooio downloads them before sending to check their size. A message whose files total more than 5 MB returns 422 message_too_large, and a file that can't be downloaded returns 422 attachment_unavailable with its url. A card shown as text sends its media as a link, which doesn't count toward the limit.

When a phone can't show it

Phones differ in what RCS features they support, and Blooio checks before sending. What happens depends on the problem:

Situation Result
The phone can't get RCS at all 422 recipient_not_rcs_capable
The phone gets RCS but can't show a card, carousel, or chip in the message 422 rcs_feature_unsupported, with the missing_features
The sender isn't live and the phone isn't an accepted test device 409 rcs_sender_not_launched
The sender isn't verified yet 409 channel_not_active

Send "fallback": "auto" (or the force-fallback header) to send what the phone can show instead of failing: cards it can't show become text with their links, and chips it can't show become lines of text, while the chips it can show stay. The response's downgraded_from says what was replaced.

To reach a phone that can't get RCS at all, route the send through a priority (below) or send it from another channel.

Routing RCS ahead of SMS

Add the sender to a priority ahead of your SMS number, and send without from. For each contact, the router skips the RCS sender when it can't reach them yet or their phone can't get RCS, and moves on to the next channel. The response's routing.fallthrough lists each skipped channel with its code (recipient_not_rcs_capable, rcs_sender_not_launched, or rcs_probe_failed).

Whether a phone gets RCS is cached per sender: a phone that does is rechecked weekly, one that doesn't is rechecked daily, and an RCS message from a phone clears a cached answer that it doesn't. To see it ahead of a send, GET /v4/contacts/{contactId}/capabilities returns an rcs entry per verified sender and phone number, with reachable and the phone's features.

Receiving

Inbound RCS messages arrive as message.received webhooks with channel_type: "rcs_business" and protocol: "rcs_business":

  • Text arrives as text.
  • A tapped suggestion arrives with the chip's label as text, and interactive.selected carries its id and title:
{
  "type": "message.received",
  "data": {
    "channel_type": "rcs_business",
    "protocol": "rcs_business",
    "text": "Change address",
    "interactive": {
      "kind": "suggestion_response",
      "role": "reply",
      "label": "Suggestion",
      "chosen": ["Change address"],
      "selected": { "id": "change_address", "title": "Change address" }
    }
  }
}
  • A shared location arrives as a Google Maps link in text.
  • A file (photo, video, or document) arrives under attachments, re-hosted at a Blooio URL.

Outbound messages report message.sent, message.delivered, message.read, or message.failed as the phone answers. Show a typing indicator with POST /v4/chats/{chat}/typing; RCS clears it on its own, so there's nothing to stop.

Opt-out and help

Carriers require every RCS sender to honor the standard keywords. Blooio handles them for you:

The person sends Blooio
STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, or QUIT Opts them out of this sender, fires contact.opted_out, and replies with the sender's opt_out_response
START or UNSTOP Opts them back in, fires contact.opted_in, and replies with the sender's opt_in_message
HELP or INFO Replies with the sender's help_response, even to someone who opted out

A send to an opted-out contact returns 409 recipient_opted_out. Opting out of one sender doesn't affect your other senders or channels. A sender that hasn't requested its launch yet has no replies to send, so it only records the opt-out or opt-in.

Webhooks

Event Fires when Payload
registration.rcs_brand.updated The brand's status changes brand_id, status
registration.rcs_sender.updated A sender's status, review sections, or carrier approvals change channel_id, status, section_statuses, carrier_approvals
registration.rcs_change.updated Blooio sends, applies, or rejects a change request The change request: change_id, kind, channel_id, status, changes, previous, reviews, note

Messages use the standard message.* events.

Pricing

A sender has a monthly fee from its submission until it's removed, its yearly verification included. Its carrier launch has a one-time fee. Each field a change request changes, a new business contact, a review appeal, and a tax ID recheck is a review, billed at one price; an expired contact PIN is billed only if it happens. Messages are billed per text segment (160 bytes of UTF-8, so 160 plain characters; reply, link, and call suggestions keep a message a text) or per rich message (files, cards, and other actions), sent or received. All of it is on your SMS, Voice & RCS invoice; see the RCS tab of Blooio's pricing for current rates. Contacts your priority reaches on another channel are billed at that channel's price.

Next steps