How it works

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/send takes text (1–4,000 characters), image_b64 (5 MB, optional caption) or audio_b64 (6 MB). Tapbacks and typing have their own endpoints.
  • Inbound media: a photo or voice message someone sends your number arrives as message.received with 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_reply otherwise), 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.

What ZeroSettle's iMessage API sends and receives (API docs, checked October 12, 2026)
TypeSendReceiveLimitsCall
TextYesYes, message.received1–4,000 characters after trimmingPOST /api/imessage/send with text
PhotoYes, caption in the same bubble; only to people who have repliedYes, with the file5 MB decoded; JPEG, PNG, GIF, HEIC, WebP, TIFF, BMPSame endpoint with image_b64
Voice messageYes, no caption; only to people who have repliedYes, as .m4a6 MB decoded; m4a, mp3, wav, aiff, caf, flac, aac, mp4, amrSame endpoint with audio_b64
TapbackYes, on any messageYes, reaction.receivedSix values; remove_ retractsPOST /api/imessage/reactions
Typing bubbleYes, in an existing conversationYes, typing.started / typing.stoppedttl_s 1–60 s, default 25POST /api/imessage/typing
Name and photoYes, shared automaticallyn/aName up to 64 characters; JPEG or PNG under 3 MBPUT /api/imessage/profile
Video, other filesNoA message row with no filen/an/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.

  1. Thu 2:14 PM
  2. Business: Photo: the framed print
    Your print is framed and ready: oak frame, white mat. Pick it up any day before 6.❤️
  3. Customer: Voice message, 0:11
  4. Business: Yes, we can frame the second print in the same oak. It takes about a week. Want us to start?
  5. Customer: yes please!👍
  6. Customer is typing
Illustrative example with a fictional framing shop. Blue bubbles are sent from the shop's number through the API; gray bubbles are the customer's. Priya has texted the shop before, which photos and voice messages require. Her voice message asks about framing a second print.
  1. Photo and caption: one POST /api/imessage/send with image_b64 and text, echoed to the webhook as message.sent with kind: "image".
  2. Priya's ❤️: reaction.received, with message.target_id set to the photo's id.
  3. Her voice message: message.received with kind: "audio"; the shop's server saves the .m4a for a person to hear.
  4. Before the reply: POST /api/imessage/typing shows the "…" bubble while someone checks the schedule, and the reply's send clears it.
  5. The 👍: POST /api/imessage/reactions with "like" and the id of her message.
  6. 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.

What ZeroSettle does to a photo before sending it (API docs, checked October 8, 2026)
You sendWhat goes outEXIF and GPS
JPEG, PNG or GIF, 2,048 px or less on the long edgeExactly as sent; a GIF keeps its animationKept, if the file has them
HEIC, WebP, TIFF or BMPRe-encoded to JPEGStripped
Any image over 2,048 px on the long edgeRe-encoded to JPEGStripped
Over 5 MB decoded, or not an accepted imageNothing: 422 invalid_requestn/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: text and audio_b64 together return 422 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.
python
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_type before you name the file. A Live Photo arrives as its still, and the caption is the text.
  • Voice messages: converted to .m4a (AAC) with duration_ms filled in.
  • Several photos in one message: only the first is delivered, so ask for one photo per message.
  • No file: image or audio is null when the inbound download never finished.
bash
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.

bash
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 returns 409 conflict.
  • Inbound: reaction.received, with message.target_id pointing at your message. It is its own event, so a receiver that acts only on message.received ignores 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.

bash
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 start followed by a send shows the bubble, then the text.
  • Long jobs: iMessage drops an indicator after about a minute of silence, so repeat start every 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) and message.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

  1. 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.
  2. ZeroSettle homepage: the feature list (text effects, "Coming soon") and the FAQ on group chats and SMS fallback. Checked October 8, 2026.
  3. Send and receive audio messages in Messages on iPhone, Apple Support (iPhone User Guide). Checked October 8, 2026.
  4. React with Tapbacks in Messages on iPhone, Apple Support (iPhone User Guide). Checked October 8, 2026.
  5. Why are my iPhone messages green?, Apple Support. Checked October 8, 2026.

Written by the ZeroSettle team. 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.

Drafted with AI assistance from ZeroSettle's API docs and homepage and Apple Support pages, as of October 8, 2026. The framing shop is a fictional business.

Text your customers from your own iMessage number

ZeroSettle gives your business its own phone number on real iMessage, running on Apple hardware we operate for you in a server facility. Your code sends with one API call and hears every reply on a signed webhook.

Pro is $49 a month with a 30-day money-back guarantee. Growth, for several numbers working as one, is from $599/mo.