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 itlegal_entity_typeisLIMITED_LIABILITY_COMPANY,CORPORATION,S_CORPORATION,PARTNERSHIP, orSOLE_PROPRIETORSHIP.organization_typeisPRIVATE_PROFIT,PUBLIC_PROFIT,NON_PROFIT, orGOVERNMENT. A public company also sendsstock_symbolasEXCHANGE:SYMBOL.street2andtitle(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 itkind |
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 ituse_caseisTRANSACTIONAL,PROMOTIONAL,OTP, orMULTI_USE.display_nameis up to 40 characters anddescriptionup to 100.brand_coloris a hex color.- The profile needs at least one of
phone,email, andwebsite, each with alabelof up to 25 characters. aliasis optional; without one, Blooio makes one from the display name. Send from either the alias or thechannel_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 itThe 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 itThe 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 itinteractionslists what the sender does:TRANSACTIONAL_UPDATES,CUSTOMER_SUPPORT,ACCOUNT_ALERTS,TWO_WAY_CONVERSATION,MARKETING_OR_PROMOTIONAL,LOYALTY_OR_REWARD.message_examplesholds 3 to 10 messages as recipients will see them.opt_in_methodsis any ofWEBSITE,MOBILE_APP,SMS,QR_CODE,SALE_POINT.WEBSITEneedscall_to_action_url, andWEBSITEorMOBILE_APPneedscall_to_action_media_url, a screenshot of where people opt in.- With
double_opt_in: true, send the confirmation request asdouble_opt_in_message. help_response,opt_out_response, andopt_in_messageare also what Blooio replies to HELP, STOP, and START (see Opt-out and help).test_video_urlis a recording of the sender in use;test_notesis 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 returns400 no_changes. - The brand takes contact changes once it's
in_revieworverified(409 rcs_brand_state_conflict). A draft sender is edited withPATCHinstead (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
402errors 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 itNode.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 itA 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 itRCS 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, andinteractive.selectedcarries itsidandtitle:
{
"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
- Message fields reference (v4): the message object, statuses, and protocols
- Number pools and priorities: put RCS ahead of SMS
- Webhooks: receive
message.*and registration events