Capabilities
Versioned feature flags each Mac advertises, why a command can be accepted and then fail as unsupported, and how to tell the capability failure codes apart.
Behind your account is a Mac running the connector. When it connects it sends a capability manifest — a versioned list of the operations that build of the connector, on that version of macOS, is able to perform. The service checks every command against that manifest before dispatching it.
You never see the manifest, and you never send capability ids yourself. But it explains a failure mode that otherwise looks arbitrary: a command that works for one account fails for another, with the same request bytes.
Why this exists
Connectors are not deployed in lockstep. Machines run different macOS releases
and different connector builds, and Apple removes and reshapes iMessage
behaviour between releases — group.leave and participant removal are both
casualties on macOS 26. Without negotiation, the service would dispatch an
operation an older or newer host cannot carry out and the command would die
somewhere unhelpful.
Capabilities move that decision forward. Each flag is versioned (.v1), so a
future host can advertise a different contract without breaking clients written
against this one.
What each command needs
| Command | Capability id |
|---|---|
message.send to a DM conversation | command.message_send.conversation.v1 |
message.send to a group conversation | command.message_send.group.v1 |
message.send to a recipient | command.message_send.recipient.v1 |
message.send with any non-text content | command.message_send.content.v1 |
reaction.tapback | command.reaction_tapback.v1 |
reaction.apply | command.reaction_apply.v1 |
name_photo.share | command.name_photo_share.v1 |
group.create | command.group_create.v1 |
group.update | command.group_update.v1 |
group.leave | command.group_leave.v1 |
Note that message.send maps to three different capabilities depending on
the target. A host can be able to reply into an existing DM while being unable
to open a cold thread to a phone number; those are separate flags.
Rich content is additive, not a replacement: a poll sent into a group needs
command.message_send.group.v1 and
command.message_send.content.v1, with poll present in that capability's
supportedKinds array. Plain text needs no content capability at all.
Flags within a capability
Some capabilities carry parameters that narrow what they allow. These are the ones that decide whether a request you can legally construct will actually run:
| Capability | Parameter | Gates |
|---|---|---|
command.message_send.content.v1 | supportedKinds | Which of rich_text, rich_link, poll, attachment, sticker may be sent |
command.reaction_tapback.v1 | supportsGroups | Tapbacks on a group conversation |
command.reaction_tapback.v1 | supportsRemove | remove: true |
command.reaction_tapback.v1 | reactions | The tapback set the host declares |
command.reaction_apply.v1 | supportsCustomEmoji | The emoji variant of the payload |
command.group_update.v1 | supportsRename | action: "rename" |
command.group_update.v1 | supportsParticipants | action: "add_participant" and "remove_participant" |
command.group_update.v1 | supportsPhoto | action: "set_photo" |
Twenty-six capability ids exist in total. The rest cover connector-internal
concerns — event delivery, history and state reads, media transfer, session
control — and operations /v1 does not expose as commands at all, so there is
nothing you can address through them from the HTTP API.
There is no discovery endpoint. Nothing on /v1 lets you list the capabilities your connector
negotiated, and no field on any response reports them. A client cannot feature-detect. If you need
to know whether your account can send polls, remove participants or use custom emoji, contact
Mapier and ask — and until then, write the code that handles the failure.
What failure looks like
The capability check happens on the way to the Mac, not at the API boundary.
So a command requesting something your host cannot do still returns 202:
{ "commandId": "cmd-1", "status": "queued", "disposition": "queued" }and then settles as failed on the
response stream with a capability
errorCode:
{
"commandId": "cmd-1",
"logicalActionId": "order-8842-confirmation",
"status": "failed",
"errorCode": "capability_not_negotiated"
}Telling the codes apart
capability_not_negotiated — the capability id is absent from the manifest
entirely. The host cannot do this operation at all. Retrying is pointless; the
identical request will fail identically until the connector is upgraded. Fall
back to something the host can do — a plain text message instead of a poll, a
tapback instead of a custom-emoji reaction.
capability_constraint_violation — the capability is present, but a
parameter in your request falls outside what it declared. A group tapback when
supportsGroups is false, remove: true when supportsRemove is false, a
rich_link when supportedKinds lists only rich_text, an emoji reaction
when supportsCustomEmoji is not set. The operation is supported; this
variant of it is not. Change the request, not the timing.
invalid_session_capabilities — the manifest itself was inconsistent, so
the session was refused. This is a fault in the connector session rather than
in your command. Treat it as transient, back off, and escalate if it persists.
unsupported_capability — the Mac declined the operation at execution
time rather than at admission. Handle it the same way as
capability_not_negotiated: do not retry unchanged.
None of the four is visible at request time — the 202 looks the same in every case — which is
the practical argument for building a fallback path rather than probing. Send the richest form you
want, and on a capability failure send the plain-text equivalent; the reader gets the message
either way.