Complete REST API and WebSocket protocol documentation for SMServer — the iMessage gateway for jailbroken iOS devices.
| Service | Default Port | Local (via iproxy) |
|---|---|---|
| HTTP REST API | 8741 |
http://localhost:8085 |
| WebSocket | 8740 |
ws://localhost:8081 |
Note: When running behind
iproxy, ports may differ (e.g.8085→8080,8081→8081). All examples use localhost with typical proxy ports.
SMServer uses password-based authentication. Once authenticated, the server tracks your IP address.
GET /requests?password=<password>Auth required: No
Response: Plain text "true" or "false"
# Authenticate (default password: toor)
curl 'http://localhost:8085/requests?password=toor'
# → trueFailed attempts incur a 2-second delay to prevent brute-force attacks.
All other endpoints require prior authentication. Unauthenticated requests return HTTP 403.
Retrieve messages from one or more conversations.
GET /requests?messages=<chat_id>&num_messages=<n>&messages_offset=<n>&read_messages=<bool>&messages_from=<0|1|2>
| Parameter | Req'd | Type | Default | Description |
|---|---|---|---|---|
messages |
Yes | string | — | Chat identifier. Comma-separate for multiple (e.g. +15551234567,email@icloud.com) |
num_messages |
No | int | app setting | Number of messages. 0 = all |
messages_offset |
No | int | 0 |
Pagination offset |
read_messages |
No | bool | false |
Mark as read when "true" |
messages_from |
No | int | 0 |
0=all, 1=sent, 2=received |
Response: JSON array of message objects.
# Messages from a phone number
curl 'http://localhost:8085/requests?messages=%2B15551234567'
# 500 messages from a group chat, mark read
curl 'http://localhost:8085/requests?messages=chat192370112946281736&num_messages=500&read_messages=true'{
"ROWID": 7,
"guid": "1899E6BF-8F30-4355-BE3B-58D2132E1618",
"date": 806862241785669100,
"date_read": 0,
"is_from_me": false,
"text": "Meow",
"subject": "",
"chat_identifier": "+15551234567",
"id": "+15551234567",
"handle_id": "1",
"service": "iMessage",
"associated_message_guid": "",
"associated_message_type": 0,
"balloon_bundle_id": "",
"cache_has_attachments": false,
"room_name": "",
"sender": "",
"imessage": true,
"item_type": 0,
"group_action_type": 0,
"link_title": "",
"link_subtitle": "",
"link_type": "",
"attachments": [
{ "filename": "IMG_9841.JPEG", "mime_type": "image/jpeg" }
]
}| Field | Type | Description |
|---|---|---|
guid |
string | Unique message identifier |
date |
int | Apple CFAbsoluteTime since 2001-01-01 |
date_read |
int | 0 if unread, else read timestamp |
is_from_me |
bool | true if sent by device owner |
text |
string | Message body |
chat_identifier |
string | Phone/email/group chat ID |
service |
string | "iMessage" or "SMS" |
cache_has_attachments |
bool | Has file attachments |
attachments |
array | {filename, mime_type} objects |
associated_message_type |
int | 0=none, types ≥2000 are tapbacks |
group_action_type |
int | 0=none, 1=added, 2=removed |
sender |
string | Sender ID (group chats only) |
Tapback values in
associated_message_type: 2000=Heart, 2001=Thumbs Up, 2002=Thumbs Down, 2003=Haha, 2004=!!, 2005=??. Values ≥3000 mean a tapback was removed.
Date → Unix:
floor(date / 1_000_000_000) + 978_307_200
GET /requests?chats=<n>&chats_offset=<n>
| Parameter | Req'd | Type | Default | Description |
|---|---|---|---|---|
chats |
Yes | — | — | Presence triggers chat listing |
chats_offset |
No | int | 0 |
Pagination offset |
Response: JSON array of chat objects.
curl 'http://localhost:8085/requests?chats&chats_offset=0'{
"has_unread": true,
"chat_identifier": "+15551234567",
"pinned": false,
"latest_text": "Meow",
"display_name": "John Doe",
"time_marker": "2:30 PM",
"relative_time": "2m ago",
"addresses": ["+15551234567"],
"is_group": false,
"members": [
{ "display_name": "John Doe", "chat_identifier": "+15551234567" }
]
}GET /requests?conversation=<chat_id>
curl 'http://localhost:8085/requests?conversation=%2B15551234567'GET /requests?name=<chat_id>
Response: Plain text display name. Falls back to the input if no contact match.
curl 'http://localhost:8085/requests?name=%2B15551234567'
# → John DoeGET /requests?search=<term>&search_case=<bool>&search_gaps=<bool>&search_group=<time|chat>
| Parameter | Req'd | Type | Default | Description |
|---|---|---|---|---|
search |
Yes | string | — | Search term |
search_case |
No | bool | "false" |
Case-sensitive |
search_gaps |
No | bool | "true" |
Spaces become wildcards when "true" |
search_group |
No | string | "time" |
"time" = sorted by recency; else grouped by chat |
Response: JSON with texts and conversations arrays.
curl 'http://localhost:8085/requests?search=hello%20world&search_group=chat'GET /requests?match=<value>&match_type=<chat|name>
| Parameter | Req'd | Type | Description |
|---|---|---|---|
match |
Yes | string | Partial value to match |
match_type |
Yes | string | "chat" = match identifiers; else = match names |
curl 'http://localhost:8085/requests?match=john&match_type=name'GET /requests?photos=<n>&photos_offset=<n>&photos_recent=<bool>
| Parameter | Req'd | Type | Default | Description |
|---|---|---|---|---|
photos |
Yes | int | — | Count (0 = app default) |
photos_offset |
No | int | 0 |
Pagination offset |
photos_recent |
No | bool | "true" |
"true" = newest first |
Response: [{is_favorite: bool, URL: string}]
curl 'http://localhost:8085/requests?photos=100'GET /requests?config
Response:
{
"socket_port": 8081,
"socket_subdirectory": "",
"debug": false,
"subjects": false
}POST /send
Content-Type: multipart/form-data
| Field | Req'd | Type | Description |
|---|---|---|---|
chat |
Yes | string | Recipient (e.g. +15551234567) |
text |
No | string | Message body |
subject |
No | string | iMessage subject line |
photos |
No | string | Colon-separated paths relative to /var/mobile/Media/ |
attachments |
No | file(s) | One or more file uploads |
Response: Status code only — 200, 400, 403, 503.
At least one of text, subject, photos, or attachments is required.
# Simple text
curl 'http://localhost:8085/send' \
-F 'chat=+15551234567' \
-F 'text=Yoo'
# With files
curl 'http://localhost:8085/send' \
-F 'chat=+15551234567' \
-F 'text=Check this out' \
-F 'attachments=@image.png' \
-F 'attachments=@doc.pdf'
# With camera roll photos
curl 'http://localhost:8085/send' \
-F 'chat=+15551234567' \
-F 'text=From camera roll' \
-F 'photos=DCIM/100APPLE/IMG_0001.JPG:DCIM/100APPLE/IMG_0002.JPG'GET /send?tapback=<0-5>&tap_guid=<guid>&remove_tap=<true>
| Parameter | Req'd | Type | Description |
|---|---|---|---|
tapback |
Yes | int | 0=❤️, 1=👍, 2=👎, 3=😂, 4=5=❓ |
tap_guid |
Yes | string | GUID with prefix (p:0/, bp:) |
remove_tap |
No | string | "true" to remove reaction |
curl 'http://localhost:8085/send?tapback=1&tap_guid=p:0/0AD2418E-19E4-47B1-9380-DB8E0A90B30C'GET /send?delete_chat=<chat_id>
curl 'http://localhost:8085/send?delete_chat=%2B11231231234'GET /send?delete_text=<guid>
curl 'http://localhost:8085/send?delete_text=p:0/4505C31A-A0FB-496F-AAA4-9821FBCF9BE4'GET /data?path=<relative_path>
path is relative to /private/var/mobile/Library/SMS/Attachments/. Supports Range headers.
curl -o attachment.jpg 'http://localhost:8085/data?path=00/D8/172BC809-BA7A-118D-18BCF0DEF/IMG_9841.JPEG'GET /data?chat_id=<identifier>
Returns 404 if no picture exists.
curl -o avatar.jpg 'http://localhost:8085/data?chat_id=%2B15204458272'GET /data?photo=<relative_path>
photo is relative to /var/mobile/Media/.
curl -o photo.jpg 'http://localhost:8085/data?photo=DCIM/109APPLE/IMG_8273.JPEG'Connect for real-time events at:
ws://localhost:8081/
SMServer uses a colon-delimited text protocol (not JSON). Each frame is a single line:
command:payload
| Direction | Format | Example |
|---|---|---|
| Server → Client | command:payload |
typing:+15551234567 |
| Client → Server | command:payload |
idle:+15551234567 |
text:{"text":{...message...}}
The payload is a JSON object with a text key containing the full message object.
text:{"text":{"guid":"1899E6BF-...","text":"Meow","chat_identifier":"+15551234567","is_from_me":false,"date":806863693330673920,...}}
typing:+15551234567
idle:+15551234567
battery:85.0
battery:charging
battery:<number> gives the percentage. battery:charging means the device is plugged in.
read:{"guid":"1899E6BF-8F30-4355-BE3B-58D2132E1618","date":"2:30 PM"}
Payload is JSON with guid and date fields.
typing:+15551234567
idle:+15551234567
Note: Messages are sent via the REST API (
POST /send), not WebSocket. The WebSocket is for real-time events and typing indicators only.
| Code | Meaning |
|---|---|
200 |
Success |
400 |
Bad request |
403 |
Not authenticated |
404 |
Not found |
406 |
Unknown query key |
503 |
Send/internal failure |
| Method | Path | Auth | Purpose | Response |
|---|---|---|---|---|
| GET | /requests?password= |
No | Authenticate | "true" / "false" |
| GET | /requests?messages= |
Yes | Get messages | JSON array |
| GET | /requests?chats= |
Yes | List conversations | JSON array |
| GET | /requests?conversation= |
Yes | Conversation details | JSON object |
| GET | /requests?name= |
Yes | Display name lookup | Plain text |
| GET | /requests?search= |
Yes | Search messages | JSON object |
| GET | /requests?match= |
Yes | Search contacts | JSON array |
| GET | /requests?photos= |
Yes | List camera roll | JSON array |
| GET | /requests?config |
Yes | Server config | JSON object |
| POST | /send |
Yes | Send message | Status code |
| GET | /send?tapback= |
Yes | Send/remove reaction | Status code |
| GET | /send?delete_chat= |
Yes | Delete conversation | Status code |
| GET | /send?delete_text= |
Yes | Delete message | Status code |
| GET | /data?path= |
Yes | Attachment file | Binary |
| GET | /data?chat_id= |
Yes | Profile picture | Binary |
| GET | /data?photo= |
Yes | Camera roll photo | Binary |
| WS | / |
No | Real-time events | JSON frames |
Apple CFAbsoluteTime → JavaScript:
const unixMs = Math.floor(date / 1_000_000_000) * 1000 + 978_307_200_000;
const jsDate = new Date(unixMs);