Skip to navigation

Messages

Use POST /v1/messages to send through your number’s messaging service: SMS/MMS, iMessage, WhatsApp, or an enabled RCS Business Messaging sender. The sending number determines which services are available. A standard SMS number does not gain iMessage by choosing a different recipient.

Messages are stored in conversations. The send response confirms acceptance; inspect message history for delivery status and deliveredChannel when the network reports a fallback. The response’s channel alone does not prove the recipient’s final transport.

In this guide

  • Channels: what SMS/MMS vs iMessage support
  • Sending messages: send text and media, and choose which number to send from
  • SMS & MMS: cellular texting, images, and 10DLC for outbound
  • iMessage: threaded replies, send effects, reactions, group chats, typing indicators, backgrounds, and limits
  • WhatsApp: templates, buttons, list menus, and the 24-hour window — see WhatsApp
  • Conversations & history: list/get/update conversations and read message history
  • Webhooks: inbound messages arrive via webhook — see Webhooks

Channels

Message history can report sms, mms, imessage, whatsapp, or rcs. Omit channel to use the selected number’s default transport. Use channel: "whatsapp" for its WhatsApp connection, or channel: "rcs" for an onboarded RCS sender. Do not pass channel: "sms" or channel: "imessage"; those are not supported request overrides. RCS is concierge-only and intentionally absent from the public schema/generated SDK types; use the HTTP examples in the RCS guide after onboarding.

SMS & MMS

Text messaging over the cellular network. Recipient reachability, country restrictions, and MMS support still apply.

  • Outbound requires SMS registration (carrier requirement for US numbers)
  • Inbound works immediately — no registration needed
  • Media sends and arrives as MMS (channel: "mms") — pass media_url or media_urls when sending

iMessage

Apple’s messaging protocol. Delivered over the internet to Apple devices, with automatic fallback to RCS or SMS for non-Apple recipients.

  • No 10DLC registration required
  • Supports reactions (tapbacks), threaded replies, send effects, rich media, image carousels, and group chats
  • channel is "imessage" for messages delivered via iMessage
  • The standard outbound-first ceiling is 50 new contacts/day/line. Replies have separate conversation and safety restrictions. See iMessage limits

WhatsApp

Your own WhatsApp Business Account, connected once through the dashboard. A separate network from the cellular and Apple channels, with its own rules.

  • Requires channel: "whatsapp" on the send. There is no automatic fallback to or from WhatsApp
  • Free-form messages only reach someone who messaged you in the last 24 hours. Outside that window you need an approved template
  • Supports quote replies, up to 3 reply buttons, list menus, link buttons, media, and reactions
  • Applicable WhatsApp message charges are billed by Meta to your WABA. See WhatsApp billing
  • Full detail in the WhatsApp guide

An iMessage line can use its fallback transport for supported non-Apple recipients. This is different from RCS Business Messaging, which uses a separately onboarded branded sender. Do not assume every iMessage feature works after fallback.

Capabilities

Check capabilities on GET /v1/conversations/{id} before enabling sending, attachments, or reactions in your UI. The API returns an error for unsupported feature/number combinations; do not rely on features being silently discarded.

FeatureSMS/MMS lineEligible iMessage lineWhatsApp
Text and mediaYes, within carrier limitsYesYes, within Meta’s window/template rules
ReactionsNoClassic tapbacks; custom emoji on supported linesYes
Group threadsAvailable on supported carrier MMS linesYesNo
Quote/threaded repliesNoOn supported linesYes
Send effectsNoYesNo
Chat backgroundsNoSupported one-to-one conversationsNo
Buttons and list menusNoNoYes

See RCS Business Messaging for branded cards, carousels, suggested replies, and SMS fallback setup.

Sending messages

Select an eligible sending number and use its default transport, or specify a supported channel override. Use explicit number_id selection when the sender matters.

POST /v1/messages

Request body

FieldTypeRequiredDescription
to_numberstringUnless using recipientsRecipient in E.164, or the returned group identifier for an existing group chat.
recipientsarrayNoStart a new supported group; mutually exclusive with to_number. See Group chats.
channelstringNowhatsapp or rcs; omit for the number’s default transport.
bodystringYesMessage text. May be an empty string when sending media only.
agent_idstringNoAgent to send as. Used to pick the sending number when from_number/number_id are omitted.
from_numberstringNoExact number to send from (E.164). Must be one of your numbers.
number_idstringNoID of the number to send from. Alternative to from_number.
media_urlstringNoA single image, video, or file URL to attach.
media_urlsarrayNoMultiple media URLs (delivered as an image carousel on iMessage).
reply_to_message_idstringNoQuote an earlier message on supported iMessage lines or WhatsApp. The parent must belong to this same number and chat. See Threaded replies.
send_stylestringNoiMessage only. Apply an expressive screen or bubble effect. See Send effects.

Choosing the sending number

Specify the sender one of three ways, in order of precedence:

  1. from_number — the exact E.164 number you want to send from.
  2. number_id — the ID of one of your numbers.
  3. agent_id — automatically selects an eligible attached number. Selection can prefer a number with richer messaging capabilities; do not rely on attachment order.

Pass at least one. Explicit sender selection takes priority over automatic selection, but supplied selectors must agree: a conflicting number or agent returns an error. A number_id used for sending must identify an attached number.

Example

curl -X POST "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_abc123",
"to_number": "+15559876543",
"body": "Your order #4521 shipped today and arrives Thursday."
}'
{
"id": "msg_abc123",
"status": "sent",
"channel": "imessage",
"from_number": "+15551234567",
"to_number": "+15559876543",
"media_urls": [],
"reply_to_message_id": null,
"reply_parent_unresolved": null
}
Python (agentphone SDK)
from agentphone import AgentPhone
client = AgentPhone(api_key="YOUR_API_KEY")
client.messages.send(
agent_id="agt_abc123",
to_number="+15559876543",
body="Your order #4521 shipped today and arrives Thursday.",
)
TypeScript (agentphone SDK)
import { AgentPhoneClient } from "agentphone";
const client = new AgentPhoneClient({ token: "YOUR_API_KEY", maxRetries: 0 });
await client.messages.sendMessage({
agent_id: "agt_abc123",
to_number: "+15559876543",
body: "Your order #4521 shipped today and arrives Thursday.",
});

Sending media

Use media_urls for attachments (do not also supply media_url). Use an empty body for media-only messages. WhatsApp and group sends accept one attachment; other supported sends accept up to 20. Some iMessage lines reject text alongside a multi-image carousel, so the example sends only images. Send accompanying text separately. Native iMessage voice memos also require one audio file and an empty body.

curl -X POST "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_abc123",
"to_number": "+15559876543",
"body": "",
"media_urls": [
"https://example.com/room-1.jpg",
"https://example.com/room-2.jpg"
]
}'

On iMessage, multiple URLs arrive as a single image carousel. Over SMS the message is delivered as MMS. See Media for how inbound attachments are represented.

Media URLs must be publicly reachable over HTTPS. Over carrier MMS, attachments must be an image (JPEG, PNG, GIF), audio (MP3, M4A, AMR, WAV), video (MP4, 3GP), vCard, or PDF, and within your sending account’s attachment limit (1 MB, 3.5 MB, or 5 MB depending on its MMS route). Keep files at or below 1 MB for the broadest compatibility — other types are rejected with a 400 because carriers won’t deliver them. iMessage accepts most common file types. Delivery of audio and video over MMS depends on the recipient’s carrier and handset; for large or unusual files, send a link instead.

SMS & MMS

SMS and MMS use the cellular network. Standard SMS lines send through their carrier route; iMessage lines can also use fallback transport when available.

  • Text from a standard SMS line is recorded as channel: "sms". Destination and account restrictions still apply.
  • Media (images, audio, video, vCards, PDFs) is sent as MMS: pass media_url or media_urls; both you and the recipient see it as channel: "mms".
  • Outbound to US numbers requires SMS registration (a carrier requirement); inbound needs no registration. See Messaging Rate Limits for throughput.
  • send_style and reply_to_message_id are rejected with 400 on unsupported standard SMS lines. Remove those fields for an ordinary SMS send.

iMessage

Use an eligible iMessage line for the rich features below. Some older lines do not support threaded replies, custom emoji, or backgrounds; unsupported requests return a clear error. Recipient/device capabilities also affect what is displayed.

iMessage limits

Outbound iMessage lines have a standard ceiling of 50 new contacts per day per line. Daily caps, unanswered-message limits, inactivity rules, and account safety pauses are separate controls. A retry does not clear a daily or reply-required limit.

See Messaging rate limits for the full rules and iMessage setup for purchasing and activation.

Threaded replies

On iMessage you can send a message as an inline reply to an earlier message, so it renders threaded under the original in the recipient’s chat. Pass the AgentPhone message ID of the parent as reply_to_message_id.

curl -X POST "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_abc123",
"to_number": "+15559876543",
"body": "Yes, Thursday works.",
"reply_to_message_id": "msg_001"
}'

The parent ID is any Message.id from the conversation, such as data.messageId on an inbound webhook message or a message from Get conversation messages. The response echoes back reply_to_message_id, plus a reply_parent_unresolved flag:

FieldMeaning
reply_to_message_idThe parent you threaded under, echoed back.
reply_parent_unresolvedtrue if the parent couldn’t be matched to a real iMessage (e.g. it was sent over SMS, or is too old). The message still sends, just not visually threaded.

Supported iMessage lines can report reply_parent_unresolved: true when the provider cannot resolve the parent. Standard SMS lines reject the field. WhatsApp quote replies use the same request field; see the WhatsApp guide.

Send effects

iMessage supports expressive send effects — the screen and bubble animations you get from the iMessage compose screen (Slam, Confetti, Fireworks, etc.). Set send_style to apply one.

curl -X POST "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_abc123",
"to_number": "+15559876543",
"body": "You are all set — see you Thursday!",
"send_style": "confetti"
}'
send_styleEffect
slamBubble slams down onto the screen
loudBubble grows large and shakes
gentleBubble arrives small and soft
invisibleInvisible ink — recipient swipes to reveal
confettiFull-screen confetti
balloonsFull-screen balloons
fireworksFull-screen fireworks
celebrationFull-screen celebration burst
lasersFull-screen laser light show
spotlightSpotlight focuses on the message
echoThe message multiplies across the screen
loveA heart inflates from the bubble

Send effects require an iMessage-capable sending number and a recipient that can display the effect. Standard SMS numbers reject send_style with 400. A fallback transport may not display the effect.

Reactions

iMessage supports tapback reactions such as hearts and thumbs up. The same endpoint also supports WhatsApp reactions. Standard SMS/MMS numbers do not support this endpoint.

Send a reaction

POST /v1/messages/{message_id}/reactions
FieldTypeRequiredDescription
reactionstringYesClassic tapback: love, like, dislike, laugh, emphasize, question. A single custom emoji is also supported on eligible iMessage lines and WhatsApp.
curl -X POST "https://api.agentphone.ai/v1/messages/msg_001/reactions" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reaction": "love"}'
{
"id": "rxn_abc123",
"reaction_type": "love",
"message_id": "msg_001",
"channel": "imessage"
}

Reacting to an image in a carousel works the same way — use the message ID of the specific image.

Reactions require an eligible iMessage line or a WhatsApp connection. Standard SMS/MMS numbers reject reaction requests with 400.

Receive reactions

When someone reacts to a message your agent sent, an agent.reaction webhook event is fired. See Webhooks for the payload format.

Group chats

Group threads use a conversation marked isGroup: true, keyed by a group identifier instead of one participant. They are supported on eligible iMessage lines and some carrier MMS lines. WhatsApp and branded RCS do not support group sending through this API. Group messages accept at most one attachment.

Group conversations and messages carry extra fields:

FieldWhereDescription
isGroupconversationtrue for group threads. Stable for the life of the thread.
groupIdconversationGroup identifier. Preserve it exactly and use it when replying; do not construct it or assume every transport uses a grp_ prefix.
groupNameconversationGroup display name when known.
groupIconUrlconversationGroup icon URL when known.
participantsconversationRoster as last seen: an array of { identifier, name }. Refreshed as members join or leave.
senderIdentifiermessageThe member who sent an inbound message. null on one-to-one messages.

Start a group

On a number that supports group creation, pass recipients instead of to_number. Eligible iMessage lines accept 2–32 distinct recipients; carrier MMS groups accept 2–10 phone numbers. Some older iMessage lines can reply to existing groups but cannot create them.

curl --fail-with-body "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer $AGENTPHONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number_id":"YOUR_NUMBER_ID","recipients":["+14155550123","+12125550199"],"body":"Welcome to the group."}'

Save the response’s to_number as the group address for subsequent sends. An unsupported line returns 422; do not retry unchanged requests.

Replying to a group

To post into the group thread, call POST /v1/messages with to_number set to the exact groupId, not an individual member’s number. Sending to a member’s number would start a separate one-to-one conversation with that person.

curl -X POST "https://api.agentphone.ai/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_abc123",
"to_number": "grp_abc123",
"body": "Friday at 7 works for me!"
}'

On supported iMessage lines, group replies can use threaded replies and send effects. To reply directly to a specific member’s message in the thread, pass that message’s ID as reply_to_message_id.

Inbound group messages are delivered to your webhook with the full group roster and the sending member inline, so you can attribute and route without a follow-up fetch. See Webhooks for the event payload and a routing example.

Typing indicator

Show a typing bubble in an iMessage conversation. This signals to the recipient that your agent is composing a response.

POST /v1/conversations/{conversation_id}/typing

The request body is empty. The indicator auto-expires after a few seconds, so there’s no “stop typing” call needed. Send it right before your agent starts generating a response.

curl -X POST "https://api.agentphone.ai/v1/conversations/conv_def456/typing" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
{
"conversationId": "conv_def456",
"channel": "imessage",
"status": "sent"
}

Typing indicators require an eligible iMessage number; standard SMS numbers return 400. A successful response confirms the request was sent, not that the recipient displayed a typing bubble.

Chat backgrounds

Set a custom background image for an iMessage conversation. The background is visible to the recipient inside the chat, and is a nice touch for branded or themed experiences.

Set a background

POST /v1/conversations/{conversation_id}/background
FieldTypeRequiredDescription
image_urlstringYesPublicly accessible URL of the background image.
curl -X POST "https://api.agentphone.ai/v1/conversations/conv_def456/background" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/brand-background.jpg"}'
{
"conversationId": "conv_def456",
"channel": "imessage",
"hasBackground": true,
"backgroundId": "bg_abc123",
"backgroundVersion": 1,
"changed": true
}

The changed flag is false when the requested image was already set, so re-sending the same background is a safe no-op. backgroundVersion increments each time the image actually changes.

Remove a background

DELETE /v1/conversations/{conversation_id}/background
curl -X DELETE "https://api.agentphone.ai/v1/conversations/conv_def456/background" \
-H "Authorization: Bearer YOUR_API_KEY"

Chat backgrounds require a supported one-to-one iMessage conversation. Standard SMS lines, unsupported iMessage lines, and group conversations return 400. Removing an eligible background returns 204 with no body.

WhatsApp

WhatsApp has enough of its own rules to get a page of its own. The short version:

  • Send with channel: "whatsapp". Everything goes through POST /v1/messages as usual
  • Free-form sends need an open 24-hour window, meaning the customer messaged you recently. GET /v1/conversations/{id} reports capabilities.whatsappWindowExpiresAt so you can check before sending
  • Outside the window, use an approved template
  • Interactive types: buttons (up to 3, with an optional media header), list (up to 10 rows), and cta for a single link button
  • Button and list taps arrive as normal inbound messages carrying replyTo, which points at the message that offered the choice

See the WhatsApp guide for templates, connecting an account, billing, and the full limits.

Conversations & history

A conversation is the thread between one of your numbers and an external participant (a single contact, or a group on iMessage). Each tracks full message history and supports custom metadata for storing session state. These endpoints are channel-agnostic.

List conversations

List all conversations for this project, sorted by most recent activity.

GET /v1/conversations

Query parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo20Number of results to return (max 100)
offsetintegerNo0Number of results to skip (min 0)

Example

curl -X GET "https://api.agentphone.ai/v1/conversations?limit=10&offset=0" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"id": "conv_def456",
"agentId": "agt_abc123",
"phoneNumberId": "num_xyz789",
"phoneNumber": "+15551234567",
"participant": "+15559876543",
"lastMessageAt": "2025-01-15T12:00:00Z",
"lastMessagePreview": "Thanks for your help!",
"messageCount": 8,
"metadata": {
"customerName": "Jane Doe",
"orderId": "ORD-12345"
},
"createdAt": "2025-01-15T11:30:00Z"
}
],
"hasMore": false,
"total": 1
}

Get conversation

Get a conversation with its recent messages.

GET /v1/conversations/{conversation_id}

Query parameters

ParameterTypeRequiredDefaultDescription
message_limitintegerNo50Number of recent messages to include (max 100)

Example

curl -X GET "https://api.agentphone.ai/v1/conversations/conv_def456?message_limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"

Update conversation

Update conversation metadata (state). Use this to store custom context for AI agents, such as customer information, business context, or session state.

The metadata is included in webhook payloads as conversationState, enabling your AI backend to maintain context across messages.

PATCH /v1/conversations/{conversation_id}

Request body

FieldTypeRequiredDescription
metadataobject or nullNoCustom key-value metadata to store with the conversation. Set to null to clear.

Example

curl -X PATCH "https://api.agentphone.ai/v1/conversations/conv_def456" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"customerName": "Jane Doe",
"orderId": "ORD-12345",
"topic": "order_tracking",
"resolved": false
}
}'

Get conversation messages

Get paginated messages for a conversation. Use before/after for cursor-based pagination through large message histories.

GET /v1/conversations/{conversation_id}/messages

Query parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo50Number of messages to return (max 200)
beforestring (datetime) or nullNonullReturn messages before this timestamp (ISO 8601)
afterstring (datetime) or nullNonullReturn messages after this timestamp (ISO 8601)

Example

curl -X GET "https://api.agentphone.ai/v1/conversations/conv_def456/messages?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"id": "msg_001",
"body": "Hi, I need help with my order",
"fromNumber": "+15559876543",
"toNumber": "+15551234567",
"direction": "inbound",
"channel": "sms",
"mediaUrl": null,
"receivedAt": "2025-01-15T11:30:00Z"
}
],
"hasMore": true
}

Message fields

FieldTypeDescription
idstringUnique message ID
bodystringMessage text (empty string for image-only messages)
fromNumberstringSender’s phone number (E.164)
toNumberstringRecipient’s phone number (E.164)
directionstring"inbound" or "outbound"
channelstring"sms", "mms", "imessage", "whatsapp", or "rcs"
statusstring or nullDelivery state, such as sent, delivered, or failed; inbound messages use received
failureReason / errorCodestring or nullFailure explanation and machine code when available
deliveredChannelstring or nullActual transport reported by a delivery receipt when different from channel
mediaUrlsarrayAll available attachment URLs; mediaUrl is the first
mediaUrlstring or nullCDN URL of an attached image, video, or file
receivedAtstringISO 8601 timestamp

Media

When someone sends an image, video, or file, the message includes a mediaUrl pointing to the hosted media. The body may be empty if only media was sent.

{
"id": "msg_002",
"body": "",
"fromNumber": "+15559876543",
"toNumber": "+15551234567",
"direction": "inbound",
"channel": "imessage",
"mediaUrl": "https://storage.googleapis.com/inbound-file-store/abc123_IMG_5414.png",
"receivedAt": "2025-01-15T12:01:00Z"
}

Read mediaUrls to handle multiple attachments, with mediaUrl as the single-attachment compatibility field. Depending on transport, multiple images can also arrive as separate message records.

Media works on both iMessage and MMS (SMS with images). When an image arrives via SMS, the channel is "mms".