Skip to main content
Every message BlueReacher sends or receives is stored as a message record you can read back by ID or list with filters. This page covers three read endpoints and the lifecycle those records move through. All three are read-only. They never change a message and never trigger a send.

Base URL and authentication

Send your API key as a bearer token on every request.
A key only ever reads messages belonging to its own workspace. Requesting a message ID from another workspace returns 404, not 403, so IDs cannot be probed across accounts.

The message object

Every endpoint on this page returns the same object shape, either whole or as a documented subset. Message records are retained for 24 months from created_at, then deleted. Requests for a deleted message return 404.

Message lifecycle

A message moves forward through the states below. It never moves backward. read and failed are terminal.

Which states each channel reaches

Not every channel reports every signal. Build your logic around what the channel can actually tell you. Because of this, sent is a normal resting state for SMS and is not a problem. Do not treat sent as a failure or retry against it.

Timing you can plan around

Inbound messages

Inbound messages are created with status set to delivered and stay there. They have no queued or sending phase, read_at is always null, and fallback is always null. Filter on direction=inbound to read only replies.

Channel fallback

BlueReacher sends on the best channel available for each number. When the requested channel cannot carry the message, the message is downgraded and sent again on the next channel down, in this order:
A downgrade is not a separate status. The message keeps one id and one lifecycle, then continues through sending, sent, and delivered on the new channel. What changes is that channel no longer matches requested_channel, and the fallback object gets filled in.

The fallback object

Fallback reasons

A fallback message is billed once, at the rate of the channel it finally went out on.

Detecting a fallback

Two equivalent checks:
To pull every downgraded message for a period, list on the channel you did not request. For example, GET /list-messages?requested_channel=imessage&channel=sms returns iMessage sends that ended up as SMS.

Failure reasons

When status is failed, the error object explains why. Retry retryable: true failures with a fresh send and a new external_id. Do not retry anything else. Re-sending to recipient_opted_out or invalid_number will fail the same way every time.

Retrieve a message

Returns the complete record for a single message, including content, media, channel history, and every lifecycle timestamp. Use this when you need the message body or fallback detail. Use /get-message-status instead when you only need to know where it stands.
Authentication: Authorization: Bearer bluereacher_your_api_key. The message must belong to your workspace.

Request fields

Passed as query string parameters. Send one or the other. Sending both returns 400 with code conflicting_identifiers.

Response fields

Returns the full message object at the top level of the response body.

Example request

Example response

200 OK
A message that fell back to SMS and then failed looks like this.

Errors

Get message status

Returns delivery state and timestamps only, without content, media, or contact detail. The payload is small and the endpoint is cheap, so use it for polling loops and dashboards.
Authentication: Authorization: Bearer bluereacher_your_api_key.

Request fields

Response fields

Example request

Example response

200 OK

Polling guidance

Webhooks are the better pattern. They push each state change to you the moment it happens and cost you no requests. Poll only when you cannot receive webhooks. If you do poll, poll no more than once every 5 seconds per message, and stop as soon as you hit a terminal state or a state you care about.

Errors

Same set as /get-message.

List messages

Returns a paginated list of messages in your workspace, newest first by default. Filter by status, channel, direction, contact, line, and date range. Use it to reconcile a campaign, pull a conversation thread, or sweep for failures.
Authentication: Authorization: Bearer bluereacher_your_api_key. Only messages in the key’s workspace are returned.

Request fields

All parameters are optional and go in the query string. Filters combine with AND. Passing both contact and contact_id returns 400 with code conflicting_filters. The maximum span between created_after and created_before is 90 days; a wider range returns 400 with code date_range_too_wide.

Response fields

Cursor pagination

Cursors are opaque, forward-only, and stable. Keep every filter identical between pages and change only cursor. Changing a filter mid-walk returns 400 with code cursor_filter_mismatch. Stop when has_more is false. Do not stop on an empty data array alone, and do not build your own cursor strings. A cursor expires 24 hours after it is issued; an expired cursor returns 400 with code cursor_expired, and you restart the walk. Records added during a walk with order=desc can be missed, since new messages land on page one behind you. For a complete sweep of a fixed window, set created_after and created_before and use order=asc.

Example request

Every failed or fallback message for one line over a two day window.

Example response

200 OK

Walking every page

Errors

An empty result set is a success, not an error. You get 200 with data: [], has_more: false, and next_cursor: null.

Error format

Every error on these endpoints returns the same JSON shape.
Branch on error.code. The message string is written for humans and its wording can change without notice.

Rate limits

Read endpoints share a limit of 600 requests per minute per API key. Every response carries the current state of your budget. A 429 response includes a Retry-After header in seconds. Wait that long, then retry with exponential backoff. Prefer one /list-messages call with limit=200 over 200 separate /get-message calls; it costs one request instead of two hundred.
  • Send a message: POST /send-message
  • Webhook events for every lifecycle change, so you can stop polling
  • Manage lines: your dedicated iMessage lines are provisioned and managed for you, with no A2P registration required