Message fields reference (v4)

Every field on a v4 multi-channel message object, plus the full status lifecycle and protocol (transport) states across iMessage, SMS, RCS, WhatsApp, and Apple Messages for Business

This is the field-by-field reference for the v4 message object (api.blooio.com/v4). v4 is a multi-channel API — the same message shape covers iMessage, SMS, RCS, WhatsApp, and Apple Messages for Business — so it adds a few transport values that don't exist in v2. For the single-channel iMessage API, see the v2 message fields reference.

Note

The v4 API is currently in beta. Field shapes are stable, but new status / protocol values may be added as channels evolve — always handle unknown values defensively.

The message object

{
  "id": "msg_018f7b2a1c3d7e4f9a2b0c1d2e3f4a5b",
  "chat_id": "chat_abc123",
  "channel_id": "ch_abc123",
  "channel_type": "blooio",
  "channel_address": "+15559876543",
  "protocol": "imessage",
  "direction": "inbound",
  "sender": "+15551234567",
  "recipient": "+15559876543",
  "contact": {
    "contact_id": "ct_018f7b2a3e4f7a5b8c6d7e8f9a0b1c2d",
    "identifier": "+15551234567",
    "name": "Jordan Lee"
  },
  "group": null,
  "type": "text",
  "text": "Hello!",
  "status": "received",
  "reply_to_message_id": null,
  "error": null,
  "attachments": [],
  "created_at": 1720992000000,
  "updated_at": 1720992003000
}
Field Type Description
id string Unique message id, prefixed msg_.
chat_id string The chat this message belongs to (chat_).
channel_id string The sending/receiving channel (ch_).
channel_type enum blooio (iMessage), twilio, whatsapp_business, or rcs_business.
channel_address string | null The address of your own line on this channel (the number or alias, e.g. +15559876543). Same regardless of direction.
protocol enum Transport that carried the message — see Protocol states.
direction enum inbound or outbound.
sender string | null Who the message is from, direction-aware. Inbound: the other party. Outbound: your own line (channel_address).
recipient string | null Who the message is to, direction-aware. Inbound: your own line (channel_address). Outbound: the other party. The mirror of sender.
contact object | null The other party (never your own line), regardless of direction — { "contact_id", "identifier", "name" }. On an inbound group message it's the member who sent it; null on a message you sent to a group. contact_id is null when no contact record matches, and name when none is on file.
group object | null { "group_id", "name" } when the chat is a group, null in a 1:1 chat.
type string Content type, e.g. text, media, poll. WhatsApp Business adds template and interactive (see the WhatsApp Business guide).
text string | null Message body. null for media-only messages.
status string Delivery lifecycle state — see Status lifecycle.
reply_to_message_id string | null The msg_ this message replies to, when it's an inline reply.
error object | null Structured error ({ code, message, ... }) when status is failed or protocol resolution failed.
attachments array Ordered attachments (url, media_type, size, optional caption). Empty array when none.
created_at int64 Epoch milliseconds when the message was created.
updated_at int64 Epoch milliseconds of the last status/protocol update.

Note

sender, recipient, contact, and channel_address are the same direction-aware endpoints delivered on the message.* webhooks — so a message reads identically whether you poll this endpoint or receive a webhook. On an inbound message, sender is the other party and recipient is your own line; on outbound they swap. contact always points at the other party. These are channel-agnostic: for whatsapp_business and amb the values are the channel's identifier/alias rather than an E.164 number, but the meaning is the same.

The send endpoints return the message at the top level (not wrapped in data) as a MessageSendResult, which adds the resolved scalar from (the sender's phone number or alias) and routing hints like group_id, hybrid, and fallback.

Status lifecycle

status describes where a message is in its delivery lifecycle, independent of which channel or transport carried it.

Status Description
queued Accepted and waiting to be handed to the channel.
sent Handed off to the channel/provider. Protocol resolves around here.
delivered A delivery receipt was received.
read A read receipt was received (where the channel supports it).
failed The message could not be delivered — see error.
superseded The row was replaced (an idempotency-key retry loser, or a poll-vote supersession) and is not an independently deliverable message.

Typical happy path:

queued → sent → delivered → read

Note

Inbound messages surface through the unified event feed and webhooks with status received. Lifecycle transitions are emitted as message.* events (message.sent, message.delivered, message.read, message.failed).

Protocol states

protocol records the transport that carried (or will carry) the message. For blooio (iMessage) channels, Blooio picks the transport at send time and resolves it shortly after; other channel types have a fixed transport.

Warning

protocol is never null. A freshly-accepted message starts at pending and resolves to a concrete transport within seconds.

Protocol Meaning
pending Accepted and dispatched; the wire service is not resolved yet. Transient — settles within seconds of sent.
imessage Delivered over iMessage (blue bubble).
rcs Delivered over RCS.
sms Delivered over SMS/MMS (green bubble).
whatsapp Sent over WhatsApp.
amb Sent over Apple Messages for Business.
unknown Accepted, but the transport could not be resolved before the tracking window closed (see error).

How protocol relates to channel_type

For fixed-transport channels the protocol is known immediately; only blooio (iMessage) sends start at pending while the device resolves the wire service.

channel_type Initial protocol Resolves to
blooio pending imessage / rcs / sms (or unknown)
twilio sms sms
rcs_business rcs rcs
whatsapp_business whatsapp whatsapp
amb amb amb

Reading protocol safely

Treat pending as "not yet known" and unknown as a terminal "accepted but unconfirmed" state. Because v4 is multi-channel and evolving, always keep a default branch for values you don't recognize.

switch (message.protocol) {
  case "pending":
    // Not resolved yet — re-read or wait for a message.* event.
    break;
  case "imessage":
  case "rcs":
  case "whatsapp":
  case "whatsapp_business":
  case "amb":
    // Rich transport confirmed.
    break;
  case "sms":
    // SMS/MMS.
    break;
  case "unknown":
    // Accepted, but transport never confirmed. Inspect `error`.
    break;
  default:
    // Forward-compatible: handle new transports gracefully.
    break;
}

Next steps