Messages

Messages are how your agent texts people: SMS and MMS over the cellular network, iMessage to Apple devices, and WhatsApp through your own WhatsApp Business Account. The same POST /v1/messages endpoint covers all of them. Across the cellular and Apple channels the platform picks for you, delivering over iMessage when it can and falling back to RCS or SMS/MMS otherwise, so Android recipients are reached automatically. WhatsApp is the exception: it is a separate network with its own rules, so you ask for it explicitly with channel: "whatsapp". Messages thread automatically into conversations, and the channel field on each message tells you how it was delivered.

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, and backgrounds
  • 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

AgentPhone sends over four channels through one endpoint, and reports which one was used in the channel field (sms, mms, imessage, or whatsapp). Between SMS/MMS and iMessage the platform picks the best one automatically. WhatsApp is opt-in per send. Your code works the same across channels, and the differences are in what features are available.

SMS & MMS

Standard text messaging over the cellular network. Works with any phone number.

  • Outbound requires 10DLC 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

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
  • Conversation charges are billed by Meta to your WABA, not to your AgentPhone credits
  • Full detail in the WhatsApp guide

Android recipients are handled for you. You send to an iMessage-capable number the same way regardless of who’s on the other end. If the recipient isn’t on an Apple device, the platform delivers over RCS or SMS automatically — you don’t select a channel or detect the recipient’s device. RCS carries most of what iMessage does; the exceptions are the Apple-only features (threaded replies, send effects, chat backgrounds) — see the capabilities table below. The channel field reflects delivery from AgentPhone’s side and reports imessage for this unified path; you don’t need a separate RCS channel to reach Android users.

Capabilities

RCS is the channel Android recipients get on the same iMessage-capable number (see the note above). It carries most of what iMessage does — the exceptions are the Apple-proprietary features, called out below.

FeatureSMSiMessageRCSWhatsApp
Send & receive textYesYesYesYes
Receive imagesYes (MMS)YesYesYes
Send imagesYes (MMS)YesYesYes
Reactions (tapbacks)YesYesYes
Group chatsYesYes
Threaded repliesYesYes
Send effects (screen & bubble)Yes
Chat backgroundsYes
Buttons & list menusYes
10DLC required for outboundYesNoNoNo

Send effects and chat backgrounds are iMessage-only (Apple features RCS doesn’t have), and threaded replies work on iMessage and WhatsApp but not RCS. They degrade gracefully: if you set send_style or reply_to_message_id on a message that ends up delivered over SMS or RCS, the text still sends and the iMessage-only behavior is simply dropped.

Sending messages

Send an outbound SMS, MMS, or iMessage with a single endpoint. The platform automatically delivers over iMessage when the recipient and your sending number both support it, and falls back to RCS or SMS/MMS otherwise. You don’t specify a channel or check the recipient’s device; Android users are reached the same way. The channel in the response tells you how it was actually delivered.

POST /v1/messages

Request body

FieldTypeRequiredDescription
to_numberstringYesRecipient in E.164 (+15559876543), or a group ID (grp_...) to post into a group chat.
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_idstringNoiMessage only. Send as an inline reply to an earlier message. 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 — sends from the agent’s first attached number.

Pass at least one. from_number and number_id win over agent_id if both are present.

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" });
await client.messages.sendMessage({
agent_id: "agt_abc123",
to_number: "+15559876543",
body: "Your order #4521 shipped today and arrives Thursday.",
});

Sending media

Attach a single file with media_url, or several with media_urls. The body can be empty if you’re only sending media.

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": "Here are the photos you asked for.",
"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 at most 5 MB — 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 are the cellular channels — they reach any phone number, Apple or not, and are the automatic fallback when iMessage isn’t available.

  • Text goes out as channel: "sms" and works with any recipient.
  • 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 10DLC registration (a carrier requirement); inbound needs no registration. See Messaging Rate Limits for throughput.
  • The iMessage-only options (send_style, reply_to_message_id) are silently ignored when a message is delivered over SMS — the text still sends.

iMessage

When the recipient and your sending number both support iMessage, messages deliver over iMessage (channel: "imessage") and unlock the rich features below. Each one degrades gracefully on SMS: the text still sends, and the iMessage-only behavior is simply dropped.

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 the id 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.

Threaded replies are an iMessage feature. On SMS the message is delivered normally without threading, and reply_parent_unresolved will be true.

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're 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 are iMessage-only. On SMS the text is delivered without any effect. Use them sparingly — they’re great for confirmations and celebratory moments, not every message.

Reactions

iMessage supports tapback reactions — the emoji responses users can add to individual messages (heart, thumbs up, etc.). Reactions are only available on iMessage channels.

Send a reaction

POST /v1/messages/{message_id}/reactions
FieldTypeRequiredDescription
reactionstringYesOne of: love, like, dislike, laugh, emphasize, question
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",
"reactionType": "love",
"messageId": "msg_001",
"channel": "imessage"
}

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

Reactions are an iMessage feature. Attempting to react to a message on an SMS number will return a 400 error.

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 chats are an iMessage feature. When your number is added to a group thread, all members’ messages flow into a single conversation marked isGroup: true, keyed on the group identifier rather than a single participant. SMS numbers cannot participate in group threads.

Group conversations and messages carry extra fields:

FieldWhereDescription
isGroupconversationtrue for group threads. Stable for the life of the thread.
groupIdconversationProvider-assigned group identifier (grp_...). Stable, and the value you send to when replying.
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.

Replying to a group

To post into the group thread, call POST /v1/messages with to_number set to the groupId (grp_...), 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!"
}'

Group replies support the same threaded replies and send effects as one-to-one iMessage. 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": "typing indicator sent"
}

Typing indicators are best-effort on iMessage only. The provider may silently drop the indicator if the chat hasn’t had recent activity, the recipient isn’t on iMessage, or it’s a group chat.

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 are an iMessage feature and apply per conversation. Setting one on an SMS conversation has no effect.

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", or "imessage"
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"
}

Image carousels (multiple images sent at once) arrive as separate messages, each with its own mediaUrl.

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