What can an iMessage API send? Photos, voice messages, tapbacks and typing
ZeroSettle's iMessage API sends text up to 4,000 characters, photos up to 5 MB with an optional caption in the same bubble, voice messages up to 6 MB that arrive as Messages' native audio bubble, tapbacks on any message, and a typing bubble. Photos and voice messages people send you reach your webhook with a link to the file. Video, files, text effects and group chats aren't supported today.
Key takeaways
- What it is: ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks.
- One send endpoint:
POST /api/imessage/sendtakestext(1–4,000 characters),image_b64(5 MB, optional caption) oraudio_b64(6 MB). Tapbacks and typing have their own endpoints. - Inbound media: a photo or voice message someone sends your number arrives as
message.receivedwith a file URL you fetch with the same bearer token. - New contacts: photos and voice messages go only to people who have replied at least once (
403 attachment_before_replyotherwise), so a first message to someone new is text, and it counts toward the 5 new contacts a day per Pro number (50 on Growth). - Price: Pro is $49/month; Growth is from $599/mo. There is no per-message charge for photos, voice messages, tapbacks or typing.
- Not supported: sending video or files, text effects, group chats, and SMS fallback.
Which message types can you send and receive?
ZeroSettle's iMessage API sends text, photos, voice messages, tapbacks, the typing bubble and your name-and-photo card, and it receives text, photos, voice messages, tapbacks and typing. Text, photos and voice messages share one send endpoint; the others have their own.
| Type | Send | Receive | Limits | Call |
|---|---|---|---|---|
| Text | Yes | Yes, message.received | 1–4,000 characters after trimming | POST /api/imessage/send with text |
| Photo | Yes, caption in the same bubble; only to people who have replied | Yes, with the file | 5 MB decoded; JPEG, PNG, GIF, HEIC, WebP, TIFF, BMP | Same endpoint with image_b64 |
| Voice message | Yes, no caption; only to people who have replied | Yes, as .m4a | 6 MB decoded; m4a, mp3, wav, aiff, caf, flac, aac, mp4, amr | Same endpoint with audio_b64 |
| Tapback | Yes, on any message | Yes, reaction.received | Six values; remove_ retracts | POST /api/imessage/reactions |
| Typing bubble | Yes, in an existing conversation | Yes, typing.started / typing.stopped | ttl_s 1–60 s, default 25 | POST /api/imessage/typing |
| Name and photo | Yes, shared automatically | n/a | Name up to 64 characters; JPEG or PNG under 3 MB | PUT /api/imessage/profile |
| Video, other files | No | A message row with no file | n/a | n/a |
Every stored row has a kind: message, image, audio or reaction. Branch on kind, not on the text: a photo sent without a caption carries the placeholder text "📷 Photo" and a voice message carries "🎤 Voice message", so an auto-responder that answers every text would reply to a picture as if the customer had typed "Photo".
ZeroSettle shares your name-and-photo card automatically with the first message of every conversation, and again with the next message after you change it. What customers see when a business texts them on iMessage shows how it looks on their side.
What does one thread look like in API calls?
Each bubble in a ZeroSettle conversation maps to one API call or webhook event. This thread from a fictional framing shop uses a photo with a caption, a voice message, a tapback in each direction and typing in each direction.
- Thu 2:14 PM
- Business: Photo: the framed print
Your print is framed and ready: oak frame, white mat. Pick it up any day before 6.❤️ - Customer: Voice message, 0:11
- Business: Yes, we can frame the second print in the same oak. It takes about a week. Want us to start?
- Customer: yes please!👍
- Customer is typing
- Photo and caption: one
POST /api/imessage/sendwithimage_b64andtext, echoed to the webhook asmessage.sentwithkind: "image". - Priya's ❤️:
reaction.received, withmessage.target_idset to the photo's id. - Her voice message:
message.receivedwithkind: "audio"; the shop's server saves the.m4afor a person to hear. - Before the reply:
POST /api/imessage/typingshows the "…" bubble while someone checks the schedule, and the reply's send clears it. - The 👍:
POST /api/imessage/reactionswith"like"and the id of her message. - Her "…":
typing.started, on the live stream and on a webhook registered with"typing_events": true.
How do photos work?
A ZeroSettle photo is POST /api/imessage/send with image_b64, up to 5 MB decoded, plus an optional text caption that arrives in the same bubble, "exactly as Messages shows a photo with a caption". ZeroSettle detects the type from the bytes.
| You send | What goes out | EXIF and GPS |
|---|---|---|
| JPEG, PNG or GIF, 2,048 px or less on the long edge | Exactly as sent; a GIF keeps its animation | Kept, if the file has them |
| HEIC, WebP, TIFF or BMP | Re-encoded to JPEG | Stripped |
| Any image over 2,048 px on the long edge | Re-encoded to JPEG | Stripped |
| Over 5 MB decoded, or not an accepted image | Nothing: 422 invalid_request | n/a |
The docs call a 1,600–2,048 px JPEG at quality 0.85 the sweet spot: "sharp on a phone screen, well under 1 MB, and sent untouched." A pass-through file keeps its metadata, so strip GPS yourself from a photo taken at a customer's address.
- Only to people who have replied: a photo to a contact who has never sent your number anything returns
403 attachment_before_reply, and nothing is sent. Send a text first and wait for their reply. - Captions take up to 4,000 characters. A link in one is tappable only once the recipient has replied or saved your number; why links in a first iMessage aren't tappable covers the workarounds.
- The console sends photos too: the camera button, or paste or drop a picture into the message box.
- A file ZeroSettle cannot decode returns
502 send_failed; re-encode it as jpg or png.
A photo plus caption is how a technician on-the-way text puts the technician's photo and arrival time in one bubble.
How do voice messages work?
A ZeroSettle voice message is POST /api/imessage/send with audio_b64, up to 6 MB decoded, and no text. It arrives as Messages' native audio bubble, with a waveform, a play button and the transcript the recipient's phone generates. Apple says audio message transcription is available in select languages.
- Only to people who have replied: like photos, a voice message to a contact who has never sent your number anything returns
403 attachment_before_reply. - Formats: m4a, mp3, wav, aiff, caf, flac, aac, mp4 and amr. ZeroSettle converts the file, and text-to-speech output works as-is. The docs name m4a (AAC) and wav as the safest inputs.
- No caption:
textandaudio_b64together return422 invalid_request. Put the words in a second message. - Length: the docs advise under a minute, and say two or three sentences land best.
- Size: if a file is near the 6 MB cap, downsample it. Speech needs nothing above 24 kHz mono, and stereo is mixed to mono anyway.
- A recording ZeroSettle cannot decode returns
502 send_failed; re-encode it as m4a or wav.
import base64, os, requests
BASE = "https://portal.zerosettle.io"
HEADERS = {"Authorization": f"Bearer {os.environ['ZEROSETTLE_TOKEN']}"}
def send_voice(to: str, path: str) -> dict:
"""Send a recording or a TTS file as a native voice bubble (no caption)."""
with open(path, "rb") as f:
audio = base64.b64encode(f.read()).decode()
r = requests.post(f"{BASE}/api/imessage/send", headers=HEADERS, timeout=120,
json={"to": to, "audio_b64": audio, "audio_format": path.rsplit(".", 1)[-1]})
return r.json()
print(send_voice("+15555550142", "pickup-note.mp3"))
The same call with image_b64 and image_format, plus text for a caption, sends a photo. Spoken lessons and spoken answers are the core of voice messages for language-learning apps.
How do photos and voice messages reach your server?
A photo or voice message someone texts your ZeroSettle number reaches your webhook as message.received with kind set to image or audio and a file URL, relative to https://portal.zerosettle.io, in image.url or audio.url. You fetch the file with the same bearer token.
- Photos: a HEIC (what iPhones shoot by default) or an oversized image is converted to JPEG; a JPEG, PNG or GIF within 4 MB and 4,032 px comes through as sent, so check
image.content_typebefore you name the file. A Live Photo arrives as its still, and the caption is thetext. - Voice messages: converted to
.m4a(AAC) withduration_msfilled in. - Several photos in one message: only the first is delivered, so ask for one photo per message.
- No file:
imageoraudioisnullwhen the inbound download never finished.
BASE=https://portal.zerosettle.io
curl -s -H "Authorization: Bearer $ZEROSETTLE_TOKEN" "$BASE/api/imessage/messages/45/image" -o photo.jpg
curl -s -H "Authorization: Bearer $ZEROSETTLE_TOKEN" "$BASE/api/imessage/messages/46/audio" -o voice.m4a
Answer 2xx within 10 seconds and download afterwards; signing and retries are in how to receive iMessages with a webhook.
How do tapbacks work?
A ZeroSettle tapback is POST /api/imessage/reactions with the message_id of any message on the line, theirs or yours, and one of six values: love ❤️, like 👍, dislike 👎, laugh 😂, emphasize ‼️ or question ❓. Prefix remove_ to retract one.
BASE=https://portal.zerosettle.io
curl -s -X POST -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H 'Content-Type: application/json' \
-d '{"message_id":44,"reaction":"like"}' $BASE/api/imessage/reactions
- Targets: text, photo and voice bubbles. A reaction to a reaction returns
404 not_found; a target that never went out returns409 conflict. - Inbound:
reaction.received, withmessage.target_idpointing at your message. It is its own event, so a receiver that acts only onmessage.receivedignores it. - The set: Messages also offers emoji, sticker and Memoji Tapbacks, and Apple's guide to Tapbacks says you can add only one per message; the API sends the six above.
- STOP: a reaction to someone who texted STOP returns
403 recipient_opted_out, and a tapback never opts anyone out.
A tapback answers a yes-or-no question in one gesture: confirm appointment reminders with a 👍, acknowledge on-call alerts, or score a one-question survey.
How do typing indicators work?
ZeroSettle's typing bubble is POST /api/imessage/typing with ttl_s from 1 to 60 seconds (default 25). It clears at that deadline or when you send anything to that person, and the call normally returns in under 2 seconds.
BASE=https://portal.zerosettle.io
curl -s -X POST -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H 'Content-Type: application/json' \
-d '{"to":"+15555550142","state":"start","ttl_s":30}' $BASE/api/imessage/typing
- Existing conversations only: the person must have texted you or been messaged by you, or the call returns
409 no_conversation, because "a '…' is not a way to probe a number." - Ordered with sends: a
startfollowed by a send shows the bubble, then the text. - Long jobs: iMessage drops an indicator after about a minute of silence, so repeat
startevery 20–30 seconds. - Free: not stored, not billed, never a new contact.
- Skip it for replies ready within about 2 seconds: "a bubble that flashes before a text reads worse than no bubble."
When the customer types, typing.started and typing.stopped are always on the live stream and opt-in on the webhook ("typing_events": true). They carry a typing object instead of a message, reach the webhook once with no retries, and come only from one-to-one iMessage conversations. The docs suggest holding a canned auto-reply while the customer is still typing.
What isn't supported yet?
ZeroSettle does not send video, other files or text effects, and does not do group chats. In the docs' words, "Voice messages and photos are the attachment types; text is the answer for everything else for now."
- Video and files: they can't be sent, and inbound ones arrive as a message row with no file. Send a link instead, once the person has replied so it is tappable.
- Text effects such as invisible ink: "Coming soon" on the ZeroSettle homepage.
- Group chats: on the roadmap; every conversation is one-to-one.
- Link previews: the console shows rich cards, but ZeroSettle does not promise how a link renders for the recipient.
- Receipts: the API reports
message.sent(the message left the line) andmessage.failed, not delivered or read status. - SMS fallback: none yet; the homepage lists it on the roadmap. Apple says a green bubble means a message went as RCS or MMS/SMS instead of iMessage; ZeroSettle sends only iMessage, so people without iMessage, such as Android users, can't be reached.
What do photos, voice messages and tapbacks cost?
Photos, voice messages, tapbacks and typing cost nothing beyond the ZeroSettle plan, because there is no per-message charge. Pro is $49/month; Growth, for several numbers working as one, is from $599/mo. Request shapes are in the attachments section of the API docs, and more explainers are in How ZeroSettle works.
Frequently asked questions
Do tapbacks, typing or name-and-photo shares count as new contacts?
No. Reactions, typing, profile shares and failed sends never count toward ZeroSettle's new-contact limit. A first text delivered to a new handle does, using one of the 5 new contacts a day each Pro number gets (50 on Growth); photos and voice messages go only to people who have already replied, so they never open a conversation.
How long does a photo or voice message take to send?
The send call returns once the line has handed the message to Apple: "normally 2–5 seconds and at most about 75 for a text (about 105 for a voice message or photo, which ZeroSettle converts first)". The docs' photo and voice examples use a 120-second HTTP timeout.
Can I send a voice message from the console?
No. The ZeroSettle console sends text and photos; voice messages go through the API with audio_b64.
Will the customer still have my voice message later?
Apple says recipients can play an audio message any time after they receive it, but must tap Keep within 2 minutes after listening to save it, unless they have set audio messages never to expire. Put an address or a time in a text as well.
Sources
- ZeroSettle iMessage API docs: "Send a message", "Attachments", "Read messages", "Typing indicators", "Webhooks, step by step" and "Errors, limits, and compliance". Checked October 12, 2026.
- ZeroSettle homepage: the feature list (text effects, "Coming soon") and the FAQ on group chats and SMS fallback. Checked October 8, 2026.
- Send and receive audio messages in Messages on iPhone, Apple Support (iPhone User Guide). Checked October 8, 2026.
- React with Tapbacks in Messages on iPhone, Apple Support (iPhone User Guide). Checked October 8, 2026.
- Why are my iPhone messages green?, Apple Support. Checked October 8, 2026.