Messages

Messages are how your agent texts people: SMS and MMS over the cellular network, and iMessage to Apple devices. The same POST /v1/messages endpoint covers every channel — the platform delivers over iMessage when it can and falls back to SMS/MMS otherwise. 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
  • Conversations & history: list/get/update conversations and read message history
  • Webhooks: inbound messages arrive via webhook — see Webhooks

Channels

AgentPhone sends over three channels through one endpoint. The platform picks the best one automatically and reports it back in the channel field (sms, mms, or imessage). Your code works the same across channels — 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
  • Images arrive as MMS (channel: "mms")

iMessage

Apple’s messaging protocol. Delivered over the internet to Apple devices, with automatic SMS fallback 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

Capabilities

FeatureSMSiMessage
Send & receive textYesYes
Receive imagesYes (MMS)Yes
Send imagesYes
Reactions (tapbacks)Yes
Threaded repliesYes
Send effects (screen & bubble)Yes
Chat backgroundsYes
Group chatsYes
10DLC required for outboundYesNo

The iMessage-only rows above degrade gracefully: if you set send_style or reply_to_message_id on a message that ends up delivered over SMS, 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 SMS/MMS otherwise. 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."
> }'
1{
2 "id": "msg_abc123",
3 "status": "sent",
4 "channel": "imessage",
5 "from_number": "+15551234567",
6 "to_number": "+15559876543",
7 "media_urls": [],
8 "reply_to_message_id": null,
9 "reply_parent_unresolved": null
10}
Python (agentphone SDK)
1from agentphone import AgentPhone
2
3client = AgentPhone(api_key="YOUR_API_KEY")
4
5client.messages.send(
6 agent_id="agt_abc123",
7 to_number="+15559876543",
8 body="Your order #4521 shipped today and arrives Thursday.",
9)
TypeScript (agentphone SDK)
1import { AgentPhoneClient } from "agentphone";
2
3const client = new AgentPhoneClient({ token: "YOUR_API_KEY" });
4
5await client.messages.sendMessage({
6 agent_id: "agt_abc123",
7 to_number: "+15559876543",
8 body: "Your order #4521 shipped today and arrives Thursday.",
9});

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.

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.
  • Images are sent as MMS: pass media_url or media_urls; to a non-iMessage recipient they arrive 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"}'
1{
2 "id": "rxn_abc123",
3 "reactionType": "love",
4 "messageId": "msg_001",
5 "channel": "imessage"
6}

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 '{}'
1{
2 "conversationId": "conv_def456",
3 "channel": "imessage",
4 "status": "typing indicator sent"
5}

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"}'
1{
2 "conversationId": "conv_def456",
3 "channel": "imessage",
4 "hasBackground": true,
5 "backgroundId": "bg_abc123",
6 "backgroundVersion": 1,
7 "changed": true
8}

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.

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"
1{
2 "data": [
3 {
4 "id": "conv_def456",
5 "agentId": "agt_abc123",
6 "phoneNumberId": "num_xyz789",
7 "phoneNumber": "+15551234567",
8 "participant": "+15559876543",
9 "lastMessageAt": "2025-01-15T12:00:00Z",
10 "lastMessagePreview": "Thanks for your help!",
11 "messageCount": 8,
12 "metadata": {
13 "customerName": "Jane Doe",
14 "orderId": "ORD-12345"
15 },
16 "createdAt": "2025-01-15T11:30:00Z"
17 }
18 ],
19 "hasMore": false,
20 "total": 1
21}

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"
1{
2 "data": [
3 {
4 "id": "msg_001",
5 "body": "Hi, I need help with my order",
6 "fromNumber": "+15559876543",
7 "toNumber": "+15551234567",
8 "direction": "inbound",
9 "channel": "sms",
10 "mediaUrl": null,
11 "receivedAt": "2025-01-15T11:30:00Z"
12 }
13 ],
14 "hasMore": true
15}

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.

1{
2 "id": "msg_002",
3 "body": "",
4 "fromNumber": "+15559876543",
5 "toNumber": "+15551234567",
6 "direction": "inbound",
7 "channel": "imessage",
8 "mediaUrl": "https://storage.googleapis.com/inbound-file-store/abc123_IMG_5414.png",
9 "receivedAt": "2025-01-15T12:01:00Z"
10}

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