/v1/messages
Queues a text from your own number. The answer is the message, usually queued; a text that can’t go right now is accepted and carries a hold saying why (quiet hours, the Mac offline, a daily limit, its turn in the pacing) and goes on its own once the hold ends, unless it expires first.
Send to for one person or chat_id for an existing group chat. send_at schedules it; a scheduled text can be changed with PATCH /v1/messages/{id} until it is due. A text to somebody on your stop list is accepted and canceled at once with cancel_reason: "opted_out".
Send text for your own words, or template_id with variables to send a saved template. A template is filled from variables, then from the person’s contact and your business name; a variable left with no value and no fallback refuses the send with invalid_request, naming it under details, and a template that isn’t yours answers not_found. The text records the template’s id as its source_ref.
Send an Idempotency-Key so a retry never sends twice: the first answer is kept 24 hours.
Needs a key with the messages:send scope.
Headers
Idempotency-KeystringAny string of up to 255 characters, new for each request. A retry with the same key gets the first answer back for 24 hours, with
Idempotent-Replayed: true.Up to 255 characters.
Request body
tostringWho to text. Send
toorchat_id, not both.3 to 320 characters.
chat_idstringAn existing group chat to text into, from
GET /v1/chats. Sendtoorchat_id, not both.Starts with
chat_.textstringThe words, up to 2000 characters. Send
textortemplate_id, not both; may be left out whenfileshas something.Up to 2,000 characters.
template_idstringSend a saved template instead of
text. Its{{variables}}are filled fromvariables, then from the person’s contact (first_name,last_name,company, custom fields) and your business name (business). A variable with no value and no fallback refuses the send, naming it.Starts with
tpl_.variablesmap of stringValues for the template’s
{{variables}}, by name. They win over the contact’s own fields. Only withtemplate_id.filesarray of stringsUp to 10 files from
POST /v1/files, sent after the words in this order.Up to 10 items.
servicestringauto(the default) uses whatever the newest chat with the person used, else iMessage, and sends an iMessage that never went once more as a regular text.imessagenever falls back;smssends a regular text through your iPhone.One of
auto,imessage,sms.Default"auto".send_attimestampSchedule it. A scheduled text is a draft you can change with
PATCH /v1/messages/{id}until it is due.expires_attimestampGive up on it if it is still waiting at this time. Default: 24 hours after it is due (a setting).
metadatamap of stringYour own data, echoed on the message and on every webhook about it: up to 20 keys of up to 40 characters, each a string of up to 500.
Example
/v1/messagescurl -X POST "https://usebluebot.com/api/v1/messages" \
-H "Authorization: Bearer $BLUEBOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70" \
-d '{
"to": "+15555550123",
"text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
"metadata": {
"crm_id": "48213"
}
}'Response
201: The text, accepted.
idstringrequiredA msg_ id.
Starts with
msg_.object"message"requireddirectionstringrequiredoutfor a text from your number,infor one to it.One of
out,in.statestringrequiredOut:
scheduled,queued,sending,sent,delivered,read,failed,unconfirmed,expired,canceled. In:received.One of
scheduled,queued,sending,sent,delivered,read,failed,unconfirmed,expired,canceled,received.tostring or nullrequiredWho it went to. For a text in, your own number when the Mac knows it. Null in a group chat.
fromstring or nullrequiredWho sent it. For a text out, your own number when the Mac knows it.
chat_idstring or nullrequiredThe group chat it is in; null for a one-to-one text.
Starts with
chat_.contact_idstring or nullrequiredThe person, or for a group text in, the member who sent it.
Starts with
ct_.conversation_idstringrequiredA conv_ id.
Starts with
conv_.textstring or nullrequiredThe words. Null for a files-only text, an unsent one, and after 30 days, when BlueBot deletes the words and keeps the facts.
text_deletedbooleanrequiredTrue once the words were deleted after 30 days.
filesarray of FilerequiredPictures and files on the text, in order.
Show its 12 fieldsHide its fields
idstringrequiredA file_ id.
Starts with
file_.object"file"requirednamestring or nullrequiredThe file name, as it was uploaded or as Messages named it.
content_typestringrequiredThe media type.
sizeintegerrequiredBytes.
kindstringrequiredWhat the file is, for showing it: an image, a video, audio, a voice memo, a PDF, a contact card or another file.
One of
image,video,audio,voice_memo,pdf,contact_card,file.statusstringrequiredreadywhen BlueBot has the bytes;waitingwhile the Mac uploads them (usually seconds);too_largefor an attachment over 25 MB, which the Mac never uploads;deletedonce 30 days have passed.One of
ready,waiting,too_large,deleted.urlstring or nullrequiredReads the file for an hour. Null unless
statusisready.url_expires_attimestamp or nullrequiredWhen
urlstops working.transcriptstring or nullrequiredFor a voice memo, the words the Mac transcribed on the device, when the owner turned transcription on.
transcript_languagestring or nullrequiredThe transcript’s language, such as
en-US.created_attimestamprequiredWhen BlueBot first knew of the file.
service_requestedstringrequiredWhat the caller asked for.
autouses the service of the newest chat with the person, else iMessage.One of
auto,imessage,sms.servicestring or nullrequiredWhat it went or came through. Null until it is sent.
One of
imessage,sms.sms_retrybooleanrequiredTrue when an iMessage that never went was sent again once as a regular text.
sourcestringrequiredWhat sent it. Texts read from Messages, including ones you typed by hand, are
mac.One of
api,zapier,make,n8n,pipedream,cli,mcp,hook,broadcast,sequence,inbox,autopilot,keyword,away,widget,assistant,test,mac.source_refstring or nullrequiredThe broadcast, sequence, hook, rule or template behind it, as an id. A text sent with
template_idcarries the template’stpl_id here.metadatamap of stringrequiredYour own data, echoed on the message and on every webhook about it: up to 20 keys of up to 40 characters, each a string of up to 500.
holdHold or nullrequiredA text that is accepted but can’t go yet carries a hold. Null when nothing holds it.
Show its 3 fieldsHide its fields
reasonstringrequiredWhy the text is waiting, checked in this order:
account_held(BlueBot paused the account),platform_stopped(BlueBot stopped all sending),plan_lapsed,paused(you paused sending),device_offline(no check-in from the Mac for 2 minutes),quiet_hours,daily_new_people_limit,platform_daily_limit(1,000 a day) andpacing(its turn in the spacing between texts).One of
account_held,platform_stopped,plan_lapsed,paused,device_offline,quiet_hours,daily_new_people_limit,platform_daily_limit,pacing.untiltimestamp or nullrequiredWhen it should stop waiting, when that is known: the end of quiet hours, the next local midnight, or the estimated slot.
descriptionstringrequiredThe hold in words.
send_attimestamp or nullrequiredFor a scheduled text, when it goes.
expires_attimestamp or nullrequiredA text still queued at this time expires.
failureobject or nullrequiredWhy a
failedtext didn’t go. Also set on asenttext when the words went and a file didn’t.Show its 2 fieldsHide its fields
reasonstringrequiredOne of
messages_refused,not_on_imessage,file_unavailable.detailstring or nullrequiredWhat the Mac saw, in words.
cancel_reasonstring or nullrequiredWhy it was canceled:
requested,opted_out(the person texted a stop word),sequence_stopped,broadcast_canceledorstaff.One of
requested,opted_out,sequence_stopped,broadcast_canceled,staff.resolutionstring or nullrequiredHow an
unconfirmedtext was settled: marked sent, or sent again as a new text.One of
marked_sent,resent.resolved_attimestamp or nullrequiredWhen it was settled.
resent_message_idstring or nullrequiredThe new text, when this one was sent again.
Starts with
msg_.reply_to_idstring or nullrequiredThe text this one answers inline, when Messages recorded one.
Starts with
msg_.reactionsarray of ReactionrequiredTapbacks on this text that are still there.
Show its 6 fieldsHide its fields
idstringrequiredA rxn_ id.
Starts with
rxn_.kindstringrequiredThe tapback: love, like, dislike, laugh, emphasize, question, or a custom emoji.
One of
love,like,dislike,laugh,emphasize,question,emoji.emojistring or nullrequiredThe emoji, for
kind: "emoji".fromstring or nullrequiredWho reacted; null when it was you, on your Mac or iPhone.
directionstringrequiredinfrom the other person,outfrom you.One of
in,out.added_attimestamprequiredWhen the tapback was added.
edited_attimestamp or nullrequiredWhen it was last edited in Messages.
unsent_attimestamp or nullrequiredWhen it was unsent in Messages.
created_attimestamprequiredWhen BlueBot accepted or first saw it.
queued_attimestamp or nullrequiredWhen it became due.
sending_attimestamp or nullrequiredWhen the Mac took it.
sent_attimestamp or nullrequiredWhen Messages showed it sent.
delivered_attimestamp or nullrequiredWhen iMessage showed it delivered. Regular texts are never delivered.
read_attimestamp or nullrequiredWhen it was read, where the person shares read receipts.
failed_attimestamp or nullrequiredWhen it failed.
unconfirmed_attimestamp or nullrequiredWhen the Mac’s lease lapsed without saying whether it went.
expired_attimestamp or nullrequiredWhen it expired.
canceled_attimestamp or nullrequiredWhen it was canceled.
received_attimestamp or nullrequiredFor a text in, when it arrived.
{
"id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
"object": "message",
"direction": "out",
"state": "queued",
"to": "+15555550123",
"from": "+15555550100",
"chat_id": null,
"contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
"conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
"text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
"text_deleted": false,
"files": [],
"service_requested": "auto",
"service": null,
"sms_retry": false,
"source": "api",
"source_ref": null,
"metadata": {
"crm_id": "48213"
},
"hold": null,
"send_at": null,
"expires_at": "2026-11-14T19:41:00.000Z",
"failure": null,
"cancel_reason": null,
"resolution": null,
"resolved_at": null,
"resent_message_id": null,
"reply_to_id": null,
"reactions": [],
"edited_at": null,
"unsent_at": null,
"created_at": "2026-11-13T19:41:00.000Z",
"queued_at": "2026-11-13T19:41:00.000Z",
"sending_at": null,
"sent_at": null,
"delivered_at": null,
"read_at": null,
"failed_at": null,
"unconfirmed_at": null,
"expired_at": null,
"canceled_at": null,
"received_at": null
}Errors
Every refusal has the same envelope and a page of its own. These are the ones this operation can give.
| Code | Status | What it means |
|---|---|---|
| 401 | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API. | |
| 403 | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope. | |
| 429 | Too many requests. A key may make 120 requests a minute. The RateLimit header says how many are left, and Retry-After how long to wait. | |
| 500 | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe. | |
| 400 | Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. details names each field. | |
| 404 | There is no such thing on this account. Ids are prefixed by kind, so a message id starts with msg_. | |
| 402 | The account’s plan isn’t active, so BlueBot isn’t taking new texts. Texts already queued wait and go once the plan is active again. | |
| 409 | The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request. | |
| 409 | The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running. | |
| 413 | The request is bigger than the endpoint takes. | |
| 415 | The body isn’t a type the endpoint reads: JSON for most, a multipart form for file uploads, and only the file types listed for files. |
Try it
Call this operation from the page with your own key. The key stays in this tab and is forgotten when you leave.