All three are read-only. They never change a message and never trigger a send.
Base URL and authentication
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 withstatus 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: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:GET /list-messages?requested_channel=imessage&channel=sms returns iMessage sends that ended up as SMS.
Failure reasons
Whenstatus 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.
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
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.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.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 onlycursor. 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.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.
Related
- 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