BetaThe V4 API is in beta — endpoints and functionality may change.

Message fields reference (v2)

Every field on a v2 message object, plus the full status lifecycle and protocol (transport) states

This is the field-by-field reference for the v2 message object (api.blooio.com/v2/api). It covers what each field means, the full set of status (delivery lifecycle) values, and the full set of protocol (transport) values. For sending mechanics and examples, see Message sending basics. Multi-channel senders (e.g. WhatsApp) use the v4 message fields reference.

The message object

{
  "message_id": "msg_abc123",
  "direction": "outbound",
  "external_id": "+15551234567",
  "internal_id": "+15559876543",
  "text": "Hello!",
  "attachments": [],
  "sender": null,
  "reactions": [],
  "time_sent": 1720992000000,
  "time_delivered": 1720992003000,
  "status": "delivered",
  "protocol": "imessage",
  "error": null
}
Field Type Description
message_id string Unique message id, prefixed msg_.
direction enum inbound or outbound.
external_id string The contact's phone number or email, or a grp_ group id for group messages.
internal_id string | null The organization sender number (from-number) used for this message.
text string | null Message body. null for attachment-only messages.
attachments array Attachment objects sent with the message.
sender string | null Sender's phone/email for inbound group messages. null for outbound and 1:1 chats.
reactions array Tapbacks and emoji reactions on this message.
time_sent int64 Epoch milliseconds when the message was sent.
time_delivered int64 | null Epoch milliseconds when delivery was confirmed. null until a receipt arrives.
status enum Delivery lifecycle state — see Status lifecycle.
protocol enum Transport that carried the message — see Protocol states.
error string | null Human-readable failure reason when status is failed (or when protocol resolution failed).
reply_to object | null Present only on inline-reply messages; omitted for top-level messages.

Status lifecycle

status describes where a message is in its delivery lifecycle. It is independent of protocol (which transport carried it).

Status Description
pending Persisted and being prepared for dispatch.
queued Accepted and waiting to be handed to Apple/the carrier.
sent Handed off to Apple/the carrier. Protocol resolves around here.
delivered A delivery receipt was received from the recipient.
failed The message could not be delivered — see error.
cancellation_requested A cancel was requested for a still-queued message (best-effort).
cancelled The message was cancelled before it was dispatched.

Typical happy path:

pending → queued → sent → delivered

Note

Inbound messages don't have a lifecycle — they're surfaced via webhooks with status received. A read receipt arrives as a separate read webhook event, not as a change to the message status column.

Checking status

Poll the status endpoint:

curl -H 'Authorization: Bearer YOUR_API_KEY' \
  'https://api.blooio.com/v2/api/chats/%2B15551234567/messages/msg_abc123/status'
Try it

Or subscribe to message.status webhooks for real-time transitions.

Protocol states

protocol records the transport that carried (or will carry) the message. Blooio picks the transport at send time — iMessage when the recipient supports it, otherwise RCS or SMS.

Warning

protocol is never null. A freshly-accepted message starts at pending and resolves to a concrete transport within seconds. The legacy non-imessage value has been removed — unresolved sends now report pending (transient) or unknown (terminal).

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 Fell back to SMS/MMS (green bubble).
unknown Accepted by the carrier, but the wire service could not be resolved before the tracking window closed (see error).

Reading protocol safely

If your integration branches on protocol, treat pending as "not yet known" rather than a final answer — re-check the status endpoint (or wait for the message.status webhook) to get the resolved transport. unknown is terminal and means the message was accepted but its transport could not be confirmed.

switch (message.protocol) {
  case "pending":
    // Not resolved yet — check again shortly.
    break;
  case "imessage":
  case "rcs":
    // Rich transport confirmed.
    break;
  case "sms":
    // Fell back to SMS/MMS.
    break;
  case "unknown":
    // Accepted, but transport never confirmed. Inspect `error`.
    break;
}

Next steps