Messages
Send and list iMessages. Unlike texts, iMessages are not scoped to a phone number you own — they are scoped to the agent identity and keyed by conversation_id. The pool line carrying a conversation is managed by Inkbox and never appears in responses.
Send message POST
POST /messagesSend an outbound iMessage to a connected recipient. Pass conversation_id to reply into an existing conversation, or to to address the connected recipient by number — exactly one of the two.
Outbound iMessages are replies by design: the recipient must have connected through the router and sent at least one message first. There is no cold outreach over iMessage.
Reply into a conversation:
Or address the connected recipient directly:
Query parameters
| Parameter | Type | Description |
|---|---|---|
agent_identity_id | UUID | The identity to send as. Required for admin API keys when sending by to; ignored for identity-scoped API keys, which always send as their own identity |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Conditional | E.164 number of a connected recipient. Mutually exclusive with conversation_id |
conversation_id | UUID | Conditional | Existing conversation to reply into. Mutually exclusive with to |
text | string | null | No | Message body, up to 18,996 characters. Required unless media_urls has an item |
media_urls | string[] | null | No | Publicly fetchable media URL to attach — at most 1 entry. Use Upload media to turn raw bytes into a URL. Required unless text is present |
send_style | string | null | No | Expressive send style applied to the message |
Preconditions
- Identity opt-in. The sending identity must have
imessage_enabled: true; otherwise sends return400. - Recipient-first contact. The recipient must be connected to this identity and have sent at least one message. Sending to an unconnected number returns
404; sending into a connection where the recipient hasn't messaged yet returns409. - Connection still active. If the recipient disconnected from the agent, sends into the old conversation return
409until they reconnect through the router. - Contact rules. Sends to a recipient blocked by the identity's contact rules return
403before anything is sent.
Rate limits
Each agent identity can send up to 100 iMessages per rolling 24-hour window. When the cap is reached, 429 responses include:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum sends allowed per identity per 24 hours |
X-RateLimit-Remaining | Remaining sends in the current window |
X-RateLimit-Reset | ISO 8601 timestamp at which the oldest send in the window will fall off |
Retry-After | Seconds until at least one slot frees up |
Response (201)
The queued message, wrapped in a message envelope. Delivery state updates asynchronously — re-read the message, or subscribe to the delivery-lifecycle webhooks (imessage.sent, imessage.delivered, imessage.delivery_failed) to track it without polling.
Error responses
| Status | Description |
|---|---|
| 400 | Sending identity is not enabled for iMessage, or agent_identity_id is missing on an admin send by to |
| 403 | Recipient is blocked by a contact rule on the identity |
| 404 | No connection exists for this recipient — the response tells you the connect command and router number to relay |
| 404 | conversation_id not found |
| 409 | The recipient hasn't messaged this agent yet, or has disconnected and must reconnect through the router |
| 422 | Invalid body: both/neither to and conversation_id, no text or media, more than one media_urls entry, or text over 18,996 characters |
| 429 | 24-hour per-identity send limit reached; respect the Retry-After header |
Delivery happens asynchronously after the 201, so provider failures never surface as send errors — they arrive as status: "error" on the message and an imessage.delivery_failed webhook.
List messages GET
GET /messagesList iMessages visible to the caller, newest first, across all of the caller's conversations or narrowed to one.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
agent_identity_id | UUID | - | Narrow to one identity. Ignored for identity-scoped API keys, which always see their own identity |
conversation_id | UUID | - | Narrow to one conversation |
limit | integer | 50 | Number of results (1-200) |
offset | integer | 0 | Pagination offset |
is_read | boolean | - | Filter by read state. Omit for all messages |
is_blocked | boolean | - | Filter by blocked state. true returns only blocked rows, false only non-blocked rows, and omitting it applies the caller's default visibility |
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.
Response (200)
Returns a list[IMessage]. Live tapbacks ride along on each message — see the Message object.
Error responses
| Status | Description |
|---|---|
| 400 | agent_identity_id names an identity that is not enabled for iMessage |
| 403 | Identity-scoped key passed an agent_identity_id other than its own |
| 404 | agent_identity_id or conversation_id not found, or not visible to the caller |
Upload media POST
POST /mediaUpload a file and get back a URL you can pass in media_urls on a send. The request is multipart/form-data with a single file field.
Request
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The file to upload, up to 10 MiB. The part's content type and filename are preserved |
Response (201)
Error responses
| Status | Description |
|---|---|
| 400 | Caller's identity is not enabled for iMessage |
| 413 | File exceeds 10 MiB |
| 502 | Upstream upload failure — safe to retry |
Send styles
Pass send_style on a send to apply one of Apple's expressive effects:
| Bubble effects | Full-screen effects |
|---|---|
slam | celebration |
loud | shooting_star |
gentle | fireworks |
invisible (invisible ink) | lasers |
love | |
confetti | |
balloons | |
spotlight | |
echo |
Send styles render on the recipient's device when the message is delivered over iMessage; they do not apply to SMS fallback.
Message object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Message ID |
conversation_id | string (UUID) | The conversation this message belongs to |
assignment_id | string (UUID) | The connection (one human ↔ one agent) carrying this conversation |
direction | string | "inbound" or "outbound" |
remote_number | string | The human's phone number (E.164). There is no local-number field — the pool line is managed by Inkbox |
content | string | null | Message body. Null for media-only messages |
message_type | string | "message", or "carousel" for rich multi-part messages |
service | string | Transport actually used: "imessage", "sms", or "rcs" |
send_style | string | null | Send style applied to the message, if any |
media | array | null | Media attachments. Each item has url, content_type, and size. URLs for stored inbound media are presigned and expire after 1 hour |
was_downgraded | boolean | null | Whether delivery fell back from iMessage to SMS |
status | string | null | Delivery lifecycle: "registered", "pending", "queued", "accepted", "sent", "delivered", "declined", "error", or "received" for inbound |
error_code | string | null | Delivery error code, when delivery failed |
error_message | string | null | Short delivery error description |
error_reason | string | null | Delivery error reason, when reported |
error_detail | string | null | Human-readable detail accompanying the error |
is_read | boolean | Read state. For inbound messages, set by mark-read |
is_blocked | boolean | Whether the message was blocked by a contact rule or by default-block in whitelist mode. Identity-scoped API keys never receive rows where this is true |
recipients | array | null | Per-recipient delivery state for outbound messages; null for inbound. Each entry has remote_number, delivery_status, service, error fields, and sent_at / delivered_at / failed_at timestamps |
reactions | array | null | Live tapbacks targeting this message, oldest first. Each entry has id, direction, reaction, custom_emoji, remote_number, part_index, and created_at |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |