Inbound messages
An inbound message reaches you in two ways:- The
message.receivedevent. iSnap sends it when it records the message.dataholds themessage, itschat_id,received_at(the time the line’s device gives for the reception) and, when the device reports one,contact_name: the display name of the sender as the device knows it. See Webhooks overview. GET /v1/messageswithdirection=inbound. It lists the inbound messages that iSnap keeps: see Listing messages and Retention.
Handle of the sender
On a line that has its own device,from holds the sender’s handle exactly as the line’s device stores it, up to 255 characters: iSnap does not normalize it. It is a phone number, or an email address for a sender who writes from the email address of their Apple ID. A phone number can come in another form than E.164, and short codes and alphanumeric senders come as they are. On a shared line, from is normalized: a phone number is in E.164.
Before you compare it with a number you send to, bring both to E.164 with the rule that POST /v1/messages applies to to: a number without a country code is read as a US number. The rules that depend on a contact having written to a line (known contacts, inbound-only lines and the reply lane of Sending limits) compare the to of a send with the handle as stored.
You cannot reply through the API to a contact who writes from an email address. POST /v1/messages and POST /v1/chats/{chat_id}/share-contact-card return 400 bad_request, the typing and read operations return 422 unsupported_handle, and a reaction returns 422 reaction_recipient_unsupported when the address is longer than 20 characters. Show such a chat as read-only.
The line’s device reports every message that its Apple ID receives, including a message sent to an email address of that account rather than to the line’s number. to is the line’s number in both cases.
When a message arrives
The line’s device checks for new messages every 2 seconds. While the device is asleep, stopped or without its permission to read messages, it reports nothing. When it is back, it reports the messages it stored meanwhile, oldest first: such a message is delayed, not lost, as long as Apple delivered it to the device. A device that is paired again does not report the messages dated more than 10 minutes before that pairing: such a message is lost, and no event announces it. A late message’screated_at is the time iSnap recorded it, later than its delivered_at.
since and until on GET /v1/messages bound created_at. A walk by created_at therefore finds a late message: see Timestamps in queries.
What iSnap records
- Long texts. iSnap cuts the text of an inbound message to 10,000 characters, counted in UTF-16 code units.
- Duplicates. A message that the device reports twice is recorded once and announced once.
- No history. Pairing a device does not import the messages that are already on it. iSnap ignores the messages, receipts and reactions that the device dates more than 10 minutes before it started serving the line.
- Attachments. The files of a message are listed in its
attachments. Read each one withGET /v1/attachments/{id}. A received file cannot go in theattachmentsofPOST /v1/messages: its id returns404 not_found.
- on a shared line, a message from a contact whom no number of the shared line serves;
- on a test line, a message from anyone but the paired contact of a trial that runs on the line;
- the message that carries a pairing code, which pairs a prospect with a trial: the pairing sends
trial.linked; - a message whose attachment the line’s device could not get within 60 seconds: the whole message is dropped, text included. See Inbound attachments.
Listing messages
GET /v1/messages lists the inbound and outbound messages of your account that iSnap still keeps, newest first by default.
sort=scheduled_for orders by scheduled_for instead of created_at, with the messages that have none, inbound messages among them, last. The cursor rules, and what happens to a cursor used with other filters, are in Pagination. With a line-scoped key, the list holds only the messages of the lines of the key.
The messages of a trial on a test line are not listed with your account key, and GET /v1/messages/{id} returns 404 not_found for them: list them with the trial key.
An entry with kind: reaction does not say which message it reacts to, nor which reaction it is. To know the reactions on a message, list with kind=message and read the reactions field of each message, or receive the reaction events: see Reactions from the contact.
GET /v1/messages/{id} returns one message with its current status. It returns 404 not_found for an unknown id, a message of another account or of a trial, a message on a line outside the list of a line-scoped key, and a message that iSnap has deleted: see Retention.
GET /v1/messages and POST /v1/messages share the message-operations limit: see Rate limits.
Chats
A chat is the exchange between one line and one contact. iSnap creates it at the first message in either direction, and uses the same chat for both directions when the line’s device stores the contact’s handle in the form of theto you send to.
What the two counters do not mean:
message_countis not the number of messages you can read: it does not go down when iSnap deletes messages.unread_countis not the number of unread messages: marking the chat read does not lower it. To know whether an inbound message was read on your side, read itsread_at: see Read receipts.
GET /v1/chats, and from the chat_id field of message.received, of message.sent with origin: "device", of the message.read that follows your own mark-read call, and of the typing indicator events.
To show the messages of one chat, list the messages of its line with GET /v1/messages and line_id, and keep those whose handle matches the chat’s contact_handle once both are in E.164: to on an outbound message, from on an inbound message. A message received in a group chat has the member’s handle in from: only the chat_id of its message.received places it in that chat. Store the chat_id of every event that carries one.
GET /v1/chats lists the chats of the lines you own, most recent activity first. line_id keeps the chats of one line, and since and until bound the time of the last activity. limit is 1 to 100, 20 by default. A chat that has a new message during a walk moves to the front of the list, before the cursor, and the walk does not return it. A message that the device reports late can move a chat back, and the walk can then return it twice. Deduplicate on the chat id, then list again with since set earlier than the start of your walk by the reporting delay that you accept. See Cursors. GET /v1/chats/{chat_id} returns one chat, or 404 not_found for an unknown chat, a chat on a line that is not yours, or a chat on a line outside the list of a line-scoped key.
Group chats
A line can receive messages in a group chat. iSnap records them in one chat for that group:- Each message has the handle of the member who sent it in
from. - The chat shows a single
contact_handle, the first sender seen. It has no member list and no name, and nothing marks it as a group. - A rename, a member who joins and a member who leaves are not reported.
- A contact card, a typing indicator or a read receipt sent on such a chat goes to that single
contact_handle.
A chat is never deleted
A chat stays listed after iSnap deletes its messages, with itscontact_handle and its counters. The API has no call that deletes a chat. See Retention.
A chat belongs to the line, not to your account. When you transfer a line, or when a line returns to the marketplace and another account gets it, the next owner lists every chat of the line, with its contact_handle, its counters and the time of its last activity, and you no longer see them. The handles of the contacts who wrote to the line stay with the line too.
The next owner can also receive the content of your messages:
- Your inbound messages that were not marked read. When the next owner marks one of these chats read, iSnap also marks read the inbound messages that you received in the chat, that it still keeps and that were not marked read. Only
POST /v1/chats/{chat_id}/readmarks a message read: when you never called it, this is every inbound message of the chat that iSnap still keeps. The line’s device can send read receipts for them to the contact, and the next owner receives each of these messages inmessage.read, with its text, the sender’s handle and the names of its files. - Events about your earlier messages. The delivery statuses, read receipts and reactions that the line’s device reports afterward about messages from before the change go to the next owner; the message events carry the message they concern. You do not receive them.
GET /v1/messages does not list your messages to the next owner, and still lists them to you until iSnap deletes them.
Before you transfer a line or let it return to the marketplace, export what you need from its chats. After you get a line, ignore the chats and the events about messages from before you got it: the counters of a chat include the messages of earlier owners, and an event whose message has a created_at earlier than the time you got the line, or a reaction whose parent_message_id is not a message of yours, concerns an earlier owner. See Lines that change owner for the rule and for that time.
Messages typed on the device
On a line that has its own device, rented or BYOD, someone can type a message in the Messages app of that device. iSnap records it:- as
direction: outboundandstatus: delivered, withsent_atanddelivered_atboth set to the device’s time; - and announces it with
message.sent, withorigin: "device"and achat_id. A message sent through the API hasorigin: "api".
message.sent with origin: "device", then deletes that copy: its id returns 404 not_found, and message_count goes down by one. Nothing in the event marks it as such a copy. Before you act on a message.sent with origin: "device" whose to and body match a message you sent through the API, wait until your own message is sent, then read the event’s message with GET /v1/messages/{id}: 404 not_found means that it was such a copy.
These messages are not recorded:
- a typed message that Apple failed to send;
- a report that iSnap did not accept within 10 minutes;
- a message typed on the device of a shared line, or of a line that runs trials.
422 parent_not_yet_acked, and a contact’s reaction to a typed message is not reported: see Chat features.
Edits, unsends and deletions
The line’s device does not report that a message was edited, unsent or deleted, whether it was received or typed on the device. iSnap keeps the message as first reported, and no event announces the change.Retention
iSnap deletes every message at least 72 hours after it recorded it, whatever its status. After that,GET /v1/messages/{id} returns 404 not_found, and the reactions on the message are deleted with it. No event announces the deletion.
The API has no call that deletes a message, a chat or an attachment. DELETE /v1/messages/{id} only cancels a send that has not left.
Once it deletes the messages, iSnap keeps:
- the chat, with its
contact_handle, its counters and the time of its last activity; - the record of each attachment, with its file name, content type and size, without the file;
- for each line, the handle of each contact who wrote to it, with the dates of their first and last inbound message.
Events
No iMessage line reports that a contact is typing: the typing indicator events are not sent for those lines.
These events do not announce everything: the messages listed in What iSnap records send nothing, and neither do edits, unsends and deletions. See What events do not announce, and reconcile from
GET /v1/messages and GET /v1/chats. Deduplicate on event_id. Delivery order is not guaranteed. Order events by the timestamps inside data, not by arrival. For a message.read about a message you sent, apply Building the status from events.
On a line that changed owner, by a transfer or by a return to the marketplace, the events about messages from before the change go to the account that owns the line when the line’s device reports them. After you get a line, ignore the events about messages from before you got it: see A chat is never deleted.
Errors
The refusals of each operation are on its reference page:GET /v1/messages, GET /v1/messages/{id}, GET /v1/chats and GET /v1/chats/{chat_id}. The errors that every operation shares are in Errors.
Personal data
Inbound messages and chats carry personal data:- the sender’s handle in
fromand the chat’scontact_handle: a phone number, or the email address of an Apple ID; - the text in
body, the files inattachmentsand their file names; contact_nameinmessage.received, the sender’s name as the line’s device knows it.
contact_handle, stays after the messages are deleted, and the API has no call that deletes it.
A chat belongs to the line, not to your account. When you transfer a line, or when a line returns to the marketplace and another account gets it, the next owner lists every chat of the line, with its contact_handle, its counters and the time of its last activity, and you no longer see them. When the next owner marks such a chat read, it receives in message.read each of your inbound messages of the chat that iSnap still keeps and that was not marked read, with its text, the sender’s handle and the names of its files. Before you transfer a line or let it return to the marketplace, export what you need from its chats: see A chat is never deleted.