Skip to the page

API reference

Send a text

Queues a text from your own number.

POST/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. Keys and scopes

Headers

  • Idempotency-Keystring

    Any 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

  • tostring

    Who to text. Send to or chat_id, not both.

    3 to 320 characters.

  • chat_idstring

    An existing group chat to text into, from GET /v1/chats. Send to or chat_id, not both.

    Starts with chat_.

  • textstring

    The words, up to 2000 characters. Send text or template_id, not both; may be left out when files has something.

    Up to 2,000 characters.

  • template_idstring

    Send a saved template instead of text. Its {{variables}} are filled from variables, 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 string

    Values for the template’s {{variables}}, by name. They win over the contact’s own fields. Only with template_id.

  • filesarray of strings

    Up to 10 files from POST /v1/files, sent after the words in this order.

    Up to 10 items.

  • servicestring

    auto (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. imessage never falls back; sms sends a regular text through your iPhone.

    One of auto, imessage, sms.Default "auto".

  • send_attimestamp

    Schedule it. A scheduled text is a draft you can change with PATCH /v1/messages/{id} until it is due.

  • expires_attimestamp

    Give up on it if it is still waiting at this time. Default: 24 hours after it is due (a setting).

  • metadatamap of string

    Your 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

POST/v1/messagesSend a text →
curl -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.

  • idstringrequired

    A msg_ id.

    Starts with msg_.

  • object"message"required
  • directionstringrequired

    out for a text from your number, in for one to it.

    One of out, in.

  • statestringrequired

    Out: 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 nullrequired

    Who it went to. For a text in, your own number when the Mac knows it. Null in a group chat.

  • fromstring or nullrequired

    Who sent it. For a text out, your own number when the Mac knows it.

  • chat_idstring or nullrequired

    The group chat it is in; null for a one-to-one text.

    Starts with chat_.

  • contact_idstring or nullrequired

    The person, or for a group text in, the member who sent it.

    Starts with ct_.

  • conversation_idstringrequired

    A conv_ id.

    Starts with conv_.

  • textstring or nullrequired

    The words. Null for a files-only text, an unsent one, and after 30 days, when BlueBot deletes the words and keeps the facts.

  • text_deletedbooleanrequired

    True once the words were deleted after 30 days.

  • filesarray of Filerequired

    Pictures and files on the text, in order.

    Show its 12 fields
    • idstringrequired

      A file_ id.

      Starts with file_.

    • object"file"required
    • namestring or nullrequired

      The file name, as it was uploaded or as Messages named it.

    • content_typestringrequired

      The media type.

    • sizeintegerrequired

      Bytes.

    • kindstringrequired

      What 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.

    • statusstringrequired

      ready when BlueBot has the bytes; waiting while the Mac uploads them (usually seconds); too_large for an attachment over 25 MB, which the Mac never uploads; deleted once 30 days have passed.

      One of ready, waiting, too_large, deleted.

    • urlstring or nullrequired

      Reads the file for an hour. Null unless status is ready.

    • url_expires_attimestamp or nullrequired

      When url stops working.

    • transcriptstring or nullrequired

      For a voice memo, the words the Mac transcribed on the device, when the owner turned transcription on.

    • transcript_languagestring or nullrequired

      The transcript’s language, such as en-US.

    • created_attimestamprequired

      When BlueBot first knew of the file.

  • service_requestedstringrequired

    What the caller asked for. auto uses the service of the newest chat with the person, else iMessage.

    One of auto, imessage, sms.

  • servicestring or nullrequired

    What it went or came through. Null until it is sent.

    One of imessage, sms.

  • sms_retrybooleanrequired

    True when an iMessage that never went was sent again once as a regular text.

  • sourcestringrequired

    What 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 nullrequired

    The broadcast, sequence, hook, rule or template behind it, as an id. A text sent with template_id carries the template’s tpl_ id here.

  • metadatamap of stringrequired

    Your 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 nullrequired

    A text that is accepted but can’t go yet carries a hold. Null when nothing holds it.

    Show its 3 fields
    • reasonstringrequired

      Why 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) and pacing (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 nullrequired

      When it should stop waiting, when that is known: the end of quiet hours, the next local midnight, or the estimated slot.

    • descriptionstringrequired

      The hold in words.

  • send_attimestamp or nullrequired

    For a scheduled text, when it goes.

  • expires_attimestamp or nullrequired

    A text still queued at this time expires.

  • failureobject or nullrequired

    Why a failed text didn’t go. Also set on a sent text when the words went and a file didn’t.

    Show its 2 fields
    • reasonstringrequired

      One of messages_refused, not_on_imessage, file_unavailable.

    • detailstring or nullrequired

      What the Mac saw, in words.

  • cancel_reasonstring or nullrequired

    Why it was canceled: requested, opted_out (the person texted a stop word), sequence_stopped, broadcast_canceled or staff.

    One of requested, opted_out, sequence_stopped, broadcast_canceled, staff.

  • resolutionstring or nullrequired

    How an unconfirmed text was settled: marked sent, or sent again as a new text.

    One of marked_sent, resent.

  • resolved_attimestamp or nullrequired

    When it was settled.

  • resent_message_idstring or nullrequired

    The new text, when this one was sent again.

    Starts with msg_.

  • reply_to_idstring or nullrequired

    The text this one answers inline, when Messages recorded one.

    Starts with msg_.

  • reactionsarray of Reactionrequired

    Tapbacks on this text that are still there.

    Show its 6 fields
    • idstringrequired

      A rxn_ id.

      Starts with rxn_.

    • kindstringrequired

      The tapback: love, like, dislike, laugh, emphasize, question, or a custom emoji.

      One of love, like, dislike, laugh, emphasize, question, emoji.

    • emojistring or nullrequired

      The emoji, for kind: "emoji".

    • fromstring or nullrequired

      Who reacted; null when it was you, on your Mac or iPhone.

    • directionstringrequired

      in from the other person, out from you.

      One of in, out.

    • added_attimestamprequired

      When the tapback was added.

  • edited_attimestamp or nullrequired

    When it was last edited in Messages.

  • unsent_attimestamp or nullrequired

    When it was unsent in Messages.

  • created_attimestamprequired

    When BlueBot accepted or first saw it.

  • queued_attimestamp or nullrequired

    When it became due.

  • sending_attimestamp or nullrequired

    When the Mac took it.

  • sent_attimestamp or nullrequired

    When Messages showed it sent.

  • delivered_attimestamp or nullrequired

    When iMessage showed it delivered. Regular texts are never delivered.

  • read_attimestamp or nullrequired

    When it was read, where the person shares read receipts.

  • failed_attimestamp or nullrequired

    When it failed.

  • unconfirmed_attimestamp or nullrequired

    When the Mac’s lease lapsed without saying whether it went.

  • expired_attimestamp or nullrequired

    When it expired.

  • canceled_attimestamp or nullrequired

    When it was canceled.

  • received_attimestamp or nullrequired

    For a text in, when it arrived.

Response201
{
  "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.

CodeStatusWhat it means
unauthorized401The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.
insufficient_scope403The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.
rate_limited429Too 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.
internal500Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.
invalid_request400Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. details names each field.
not_found404There is no such thing on this account. Ids are prefixed by kind, so a message id starts with msg_.
plan_lapsed402The 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.
idempotency_conflict409The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request.
conflict409The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running.
payload_too_large413The request is bigger than the endpoint takes.
unsupported_media_type415The 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.