conversation_id; callers never choose a sending number in the message request. Shared lines remain hidden, while an attached dedicated line lets the identity start a conversation or group.
Get message
agent_identity_id to restrict the lookup to that identity. Identity-scoped
credentials remain restricted to their own visible messages. An inaccessible or
unknown message returns 404.
error_code: "send_outcome_ambiguous" means delivery could not be confirmed. After
a 15-minute confirmation window, the message can have status: "error" and
error_reason: "delivery_unconfirmed". This is not proof that it was never
delivered. Keep the original message rather than sending another copy; a later
delivery update can still resolve it.
Send message
SendIdempotency-Key with Prefer: idempotency-replay to protect retries. The same key and
input replay the original queued response and message ID. Read the message for
current delivery status; do not create another message just because delivery is
pending. See message send retries and lookup.
conversation_id to reply into an existing conversation, or to as either one E.164 phone number or a list of 1-8 E.164 phone numbers — exactly one of the two.
A dedicated line can receive messages from non-phone sender addresses. Those conversations are receive-only: sending by conversation_id returns 422 with detail.error: "recipient_not_e164". The to field still requires E.164 phone numbers.
Standard shared-service identities can reply only after the recipient has started a conversation. A dedicated line can start a new 1:1 conversation or a group chat; lists with 2-8 recipients select group behavior, while a scalar or one-item list keeps the 1:1 behavior.
Organizations with their own organization-owned shared pool can submit the first message
through POST /connect, available
only by request. Subsequent sends use this message endpoint. Until the recipient
replies, they return 409 with error: "imessage_awaiting_inbound", unless prior
direct engagement on the assigned number already satisfies the reply requirement.
Reply into a conversation:
JSON
JSON
JSON
409. A different to set never edits an existing group’s membership. Keep the returned conversation_id and use it to continue the exact same thread. See Group chats for membership and delivery semantics.
Query parameters
Request body
Preconditions
- Identity opt-in. The sending identity must have
imessage_enabled: true; otherwise sends return400. - Line capability. Standard shared service requires recipient-first contact. A dedicated line can start 1:1 conversations and is required for groups.
- Group participants. Group
tolists contain 2-8 distinct remote E.164 phone numbers and cannot include the attached sending line. Every participant must pass opt-out and contact-rule checks before the message is queued. - Connection still active. If the recipient disconnected from the agent, sends into the old conversation return
409. Reconnect through the router for standard shared service, or through connect for a organization-owned shared pool, subject to its eligibility checks. - Contact rules. Sends to a recipient blocked by the identity’s contact rules return
403before anything is sent.
Before the first reply
Before a qualifying recipient reply, every 1:1 message must contain fewer than 160 characters (159 maximum), without phone numbers, links, or media. These content requirements apply to all line types, including dedicated lines. They do not apply to group sends or grant permission to send before a connection is ready. At most three 1:1 messages total are allowed before the recipient’s first qualifying reply. When this limit rejects a send, the API returns422 imessage_unanswered_followup_limit; wait for a reply before sending again. This ceiling does not grant additional sends when a connection requires an earlier reply. The three-message cap ends after the first qualifying reply, but other unanswered-message limits still apply.
Violations return 422 before the message is queued, with the error code in detail.error. A numeric identifier that resembles a phone number can also produce imessage_reply_required_for_phone_numbers. Wait for the recipient’s reply before including that identifier. After a qualifying reply, these pre-reply content restrictions no longer apply; other send requirements and limits still do.
Rate limits
Each agent identity can send up to 100 iMessages per rolling 24-hour window. When the cap is reached,429 responses include:
Dedicated lines carry additional guardrails on agent-initiated conversations. A dedicated line can start conversations with at most 10 new recipients per rolling 1-hour window and 40 new recipients per rolling 24-hour window, which keeps the line in good standing. Both limits apply only to recipients the line hasn’t reached before:
- It does not apply to a recipient the agent has already connected with on that line.
- It does not apply when the recipient messages the line first.
429 with error: "outbound_line_new_contact_rate_limited" and the same Retry-After and X-RateLimit-* headers. Those headers describe the limit that rejected the request, and Retry-After counts down until enough new-recipient slots in that window age out.
Response (201)
The queued message, wrapped in amessage envelope. Delivery state updates asynchronously. Re-read the message to observe every outcome. Delivery-lifecycle webhooks (imessage.sent, imessage.delivered, imessage.delivery_failed) report delivery updates, but do not cover every failure before sending.
JSON
Error responses
The
404 and 409 connection errors carry a structured detail — read detail.error to branch and detail.message for the sentence to relay:
The expiry error returns
409 whether you address the expired connection by
conversation_id or the recipient’s to number. It does not expose the previous
shared sending number. Conversation history stays readable. See
inactive shared connections
for the default expiry policy.
Compatibility note: a send addressed by to whose latest connection expired
now returns 409 imessage_connection_expired instead of
404 imessage_no_assignment. Update any handling that assumes every recipient
without an active connection returns 404. Ordinary missing connections and
manual disconnects retain their existing error behavior.
Reactions, read receipts, and typing indicators also reject inactive connections.
They return imessage_connection_expired when the connection expired due to
inactivity.
Delivery happens asynchronously after the 201, so later failures do not change the send response. Re-read the message to observe every outcome. Delivery updates can emit webhooks, but do not cover every failure before sending. If a reply target becomes unavailable before sending, the message reports status: "error" and error_code: "imessage_reply_target_unavailable".
The first message in a new group also creates the group asynchronously. Check the conversation’s group_creation_status: "creating" while that attempt resolves, "ready" after success, or "not_created" when no confirmed group is available. During creating, later messages wait for the first send. If creation remains unconfirmed after 15 minutes, the first message gets an unknown-delivery outcome. Waiting messages that were never sent get error_code: "group_creation_unconfirmed" and error_reason: "request_not_sent". A fresh explicit send can attempt group creation again; the uncertain original is never automatically resent. All attempts remain in the conversation’s history. Existing-group membership is rechecked before delivery, so a change after the 201 can move the queued message to status: "error". A later conversation_id send resolves its current membership again.
List messages
Query parameters
Identity-scoped API keys never see contact-rule-blocked messages, regardless of
is_blocked. Admin API keys and the Inkbox Console see blocked and non-blocked messages by default; use is_blocked=true for a blocked-only audit listing.
start_datetime, end_datetime, and tz filter on created_at. Both bounds are optional and non-breaking — omit them for unchanged behavior. See Filtering by date.
Response (200)
Returns alist[IMessage]. Live tapbacks ride along on each message — see the Message object. Within an accessible conversation, an unknown or inaccessible thread_id returns 200 with []. In contrast, Get conversation thread returns 404 when that thread is not visible.
JSON
Error responses
Reply to a specific message
Native replies work within both 1:1 and group iMessage conversations. A conversation is the whole chat; a reply thread groups a message and its replies. Ordinary conversation message lists remain flat and include all messages. Pass the existingconversation_id and the message’s Inkbox UUID as reply_to_message_id:
JSON
plain_reply_fallback defaults to true. Inkbox uses a native reply when supported. If the target is known to use SMS/RCS or was downgraded, Inkbox sends an ordinary message in the same conversation, using normal transport selection. This does not force SMS/RCS, create a new conversation, or change a group’s audience. Set plain_reply_fallback: false to require native threading instead; an unsupported target then returns 422.
Fallback is decided before sending, from what is known about the target. If the target was recorded as iMessage but native delivery is later rejected, Inkbox does not retry it as an ordinary message, even with plain_reply_fallback: true. A delivery failure, timeout, or uncertain result also does not trigger an automatic resend.
A queued response is not confirmation of delivery. Poll GET /messages/{message_id} for the current status and error fields. If the target becomes unavailable before sending, the message reports status: "error" and error_code: "imessage_reply_target_unavailable". Delivery webhooks do not cover every failure before sending, so they supplement message reads rather than replace them.
An ordinary fallback has no reply_to_message_id and does not join the target’s native thread. It may have its own standalone thread_id. A queued response can initially contain the requested native parent; an allowed ordinary fallback decided before sending can clear that relationship. Re-read the message for current metadata rather than treating the requested target or initial queued response as the final reply relationship.
Omit reply_to_message_id or pass null for the existing ordinary-message behavior; plain_reply_fallback has no effect then. A reply remains one message in the same conversation, with the same usage accounting and send limits. Changing the target or its fallback policy changes the send input: use a new idempotency key for a different intended send.
Each message can expose three nullable fields:
reply_to_message_id: its immediate parent message, when visible and resolved. A non-null value identifies a reply, not an ordinary message.thread_root_message_id: the message that started the thread. For a standalone message, this can be its ownid.thread_id: the opaque identifier used to retrieve the whole thread. This is not the root message’s ID, and its presence does not mean replies exist.
thread_id before anyone replies. Reading that thread returns the message itself. A null parent alone does not prove a message is standalone: a reply’s parent can be unresolved or hidden.
If B replies to A and C replies to B, their immediate parents differ, but A, B, and C belong to the same thread. Incoming native replies preserve these relationships in 1:1 and group chats. Fields can be null while a message is pending or a reference is unresolved or not visible to you. Older message history might not have thread metadata; clients must tolerate missing or null fields.
Get a reply thread
Response (200)
Continue with the returned cursor on the same thread and conversation. Do not parse or construct cursors yourself. Reads only return history you can access; they do not fetch missing messages from participants’ devices. An inaccessible or unknown anchor returns
404; an invalid cursor or pagination parameter returns 422.
If you already have conversation_id and thread_id, use Get conversation thread. For the existing newest-first list shape and offset pagination, use GET /messages?conversation_id=...&thread_id=... instead.
Upload media
media_urls on a send. The request is multipart/form-data with a single file field.
Request
Response (201)
JSON
Error responses
Send styles
Passsend_style on a 1:1 or group send to apply one of Apple’s expressive effects. Dedicated-line groups support styles on both creation and conversation_id replies, including messages that also carry the single allowed media item:
Send styles render on the recipient’s device when the message is delivered over iMessage; they do not apply to SMS fallback.

