The BlueBubbles Server can operate in two modes: a standard mode driven by AppleScript automation, and an enhanced mode that uses a private macOS framework to communicate directly with the Messages app at a lower level. This lower-level integration is called the Private API. It unlocks a set of features that AppleScript simply cannot reach — reactions, typing indicators, group management, and more — but it requires explicit opt-in on the server side before your SDK calls can use them.
The Private API features described on this page only work when Private API mode is enabled in your BlueBubbles Server settings. If the server does not have Private API enabled, calls to Private API-backed methods will fail or silently fall back to standard behavior. Enable it in the BlueBubbles Server app under Settings → Private API.
What the Private API unlocks
The following features are only available when the Private API is active on the server:
| Feature | SDK method(s) |
|---|
| Create new chats | client.chats.create() |
| Add participants to a group | client.chats.addParticipantToChat() |
| Remove participants from a group | client.chats.removeParticipantFromChat() |
| Send tapback reactions | client.messages.sendReaction() |
| Start typing indicators | client.chats.startSendTypingIndicator() |
| Stop typing indicators | client.chats.stopSendTypingIndicator() |
| Mark chat as read | client.chats.markRead() |
| Mark chat as unread | client.chats.markChatAsUnread() (requires macOS Ventura or later) |
| Rename a group chat | client.chats.updateAChat() |
| Set group chat icon | client.chats.setGroupIcon() |
| Remove group chat icon | client.chats.removeGroupIcon() |
| Leave a group chat | client.chats.leaveChat() |
| Delete a chat | client.chats.deleteChat() |
| Send with message effects | client.messages.sendText() with effectId |
| Send with a subject line | client.messages.sendText() with subject |
| Reply to a specific message | client.messages.sendText() with selectedMessageGuid |
Sending with method: "private-api"
When you call client.messages.sendText(), the server uses AppleScript by default. To explicitly route the send through the Private API — which enables richer features and better reliability — pass method: "private-api" in the request body.
await client.messages.sendText({
requestBody: {
chatGuid: "iMessage;+;+15550001234",
message: "Hello with Private API!",
tempGuid: crypto.randomUUID(),
method: "private-api",
},
});
Some parameters automatically force the Private API even without setting method explicitly. These are:
subject — attaches a subject line to the message
effectId — sends the message with an iMessage effect (e.g., balloons, confetti)
selectedMessageGuid — sends the message as a reply to a specific prior message
// Send with a subject — automatically uses Private API
await client.messages.sendText({
requestBody: {
chatGuid: "iMessage;-;tim@apple.com",
message: "Check out this announcement.",
subject: "Important Update",
tempGuid: crypto.randomUUID(),
},
});
// Send with an iMessage effect — automatically uses Private API
await client.messages.sendText({
requestBody: {
chatGuid: "iMessage;+;+15550001234",
message: "Happy Birthday!",
effectId: "com.apple.MobileSMS.expressivesend.balloons",
tempGuid: crypto.randomUUID(),
},
});
// Reply to a specific message — automatically uses Private API
await client.messages.sendText({
requestBody: {
chatGuid: "iMessage;+;+15550001234",
message: "Agreed!",
selectedMessageGuid: "p:0/ABCDEF01-2345-6789-ABCD-EF0123456789",
partIndex: 0,
tempGuid: crypto.randomUUID(),
},
});
Sending reactions
Reactions (tapbacks) are exclusively a Private API feature. Provide the chatGuid, the GUID of the message you’re reacting to, and one of the supported reaction strings.
await client.messages.sendReaction({
requestBody: {
chatGuid: "iMessage;+;+15550001234",
selectedMessageGuid: "p:0/ABCDEF01-2345-6789-ABCD-EF0123456789",
reaction: "love",
partIndex: 0,
},
});
Valid reaction values:
| To add | To remove |
|---|
love | -love |
like | -like |
dislike | -dislike |
laugh | -laugh |
emphasize | -emphasize |
question | -question |
Prefix the reaction name with - to remove a previously sent tapback.
Managing group participants
Adding and removing participants from a group chat both require the Private API. Include the participant’s phone number or email address (with country code for phone numbers).
const chatGuid = "iMessage;+;chat012345678901234567";
// Add a participant
await client.chats.addParticipantToChat({
chatGuid,
requestBody: {
address: "+15550005678",
},
});
// Remove a participant
await client.chats.removeParticipantFromChat({
chatGuid,
});
Include the country code when specifying phone number addresses. For US numbers, prefix with 1 — for example, +15550005678.
Typing indicators
You can show and hide the typing indicator in a conversation. The indicator automatically clears when a message is sent to that chat.
const chatGuid = "iMessage;+;+15550001234";
// Show the typing indicator
await client.chats.startSendTypingIndicator({ chatGuid, requestBody: null });
// ... compose the message ...
// Hide the typing indicator explicitly (also clears on send)
await client.chats.stopSendTypingIndicator({ chatGuid });
Creating a new chat
Creating a new iMessage conversation requires the Private API. Pass an array of participant addresses and an optional opening message.
const newChat = await client.chats.create({
requestBody: {
addresses: ["+15550001234", "+15550005678"],
message: "Hey everyone, welcome to the group!",
},
});
console.log(newChat.data.guid); // "iMessage;+;chat..."
Marking chats as read and unread
Mark as read
Calling markRead marks the chat as read on the macOS server and notifies other BlueBubbles clients to update their unread state.await client.chats.markRead({
chatGuid: "iMessage;+;+15550001234",
requestBody: {},
});
Mark as unread
Calling markChatAsUnread marks the chat as unread. On macOS Ventura and later, this also updates the unread state for other Apple devices signed into the same account. On earlier macOS versions, the event is dispatched to BlueBubbles clients only.await client.chats.markChatAsUnread({
chatGuid: "iMessage;+;+15550001234",
requestBody: {},
});
markChatAsUnread requires macOS Ventura or later for the change to propagate to iOS devices. On earlier macOS versions, only BlueBubbles clients will reflect the unread state.
Minimum server version requirements
Some Private API features require a specific minimum version of the BlueBubbles Server:
| Feature | Minimum Server Version |
|---|
| Create chat | 0.3.0 |
| Add / remove participant | 0.3.0 |
| Rename group chat | 0.3.0 |
| Mark chat as read | 1.1.0 |
| Delete chat | 1.3.0 |
| Mark chat as unread | 1.4.0 |
partIndex on reactions | 1.4.0 |
You can check the running server version at any time by calling await client.server.getServerMetadata() and inspecting the version field in the response.