mapier docs

Reactions

Choosing between the two reaction commands, and reading the outcome.

There are two reaction commands, and the first thing to settle is which one you need. They are not two versions of the same thing: they target different messages, work in different places, and one of them cannot remove a reaction at all.

Which command

reaction.tapbackreaction.apply
Works in groupsYesNo — direct messages only
Which messageAny message you name by externalIdOnly the newest incoming message
Remove a reactionYes, with remove: trueNo
Custom emojiNo — the classic six onlyYes, 1 to 32 characters
ThroughputSuitable for volumeOne at a time per Mac

Use reaction.tapback. It is the default for every case except one.

reaction.apply earns its place for a single reason: it is the only command that can react with an arbitrary emoji. If you are applying one of the six classic tapbacks, there is nothing it does that tapback does not do better.

The classic six

Both commands accept the same six reaction names, which are the tapbacks Messages itself offers:

love · like · dislike · laugh · emphasis · question

Anything outside that set fails, but not where you would expect: payloads are not validated at the API boundary, so an unrecognised reaction still comes back 202 and then settles failed on the stream. An arbitrary emoji goes through reaction.apply instead, and needs a Mac that advertises custom-emoji support — see Capabilities.

Reacting to a message

You react to a message by its externalId, which is the iMessage GUID that arrives on the message event. Keep that value alongside the conversationId when you process inbound messages; you need both to react.

curl $MAPIER_BASE_URL/v1/commands/reaction.tapback \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: react-guid-1-love" \
  -d '{
    "target": {
      "kind": "conversation",
      "conversationId": "a0000000-0000-4000-8000-000000000001"
    },
    "payload": { "reaction": "love", "targetMessageExternalId": "guid-1" }
  }'

Reactions take the agent-turn routing path, so the Idempotency-Key above is honoured: a retry adopts the original command rather than reacting twice. A key built from the message id and the reaction, as here, is naturally unique per intent — but the constraint is permanent, so if you later remove that reaction and want to put it back, the same key adopts the finished command and nothing happens. Add something that changes to the key whenever a repeat is a real intent rather than a retry. See Idempotency.

The response is a 202 — the command is queued, not applied. The outcome arrives on the response stream.

React promptly

stale_target is the failure you will actually hit. The executor checks that the target GUID is still present in the conversation's bounded history before it fires, and fails closed if it is not. React while the message is recent — a reaction issued hours later, or after a long backlog, may find nothing to attach to.

Removing a reaction

Same command, same target message, plus remove:

{ "reaction": "love", "targetMessageExternalId": "guid-1", "remove": true }

You must name the reaction you are removing, not only the message. Removal is only available on reaction.tapback; reaction.apply has no equivalent, which is one more reason to prefer tapback if there is any chance you will want to undo.

Custom emoji

reaction.apply reacts to whichever message is currently newest and incoming in a direct message. Because that target moves as new messages arrive, you name the message you believe is newest, and the command refuses to run if it has been overtaken — it settles stale_target instead, so you never react to the wrong bubble.

curl $MAPIER_BASE_URL/v1/commands/reaction.apply \
  -H "Authorization: Bearer $MAPIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": {
      "kind": "conversation",
      "conversationId": "a0000000-0000-4000-8000-000000000001"
    },
    "payload": {
      "emoji": "🎉",
      "expectedNewestIncomingMessageExternalId": "guid-1"
    }
  }'

The payload has a second branch that takes reaction with one of the classic six in place of emoji. It is there for completeness; there is no reason to choose it over tapback.

reaction.apply does not scale

This command drives the Mac's user interface directly, and only one such reaction runs at a time per host. A burst of them queues up behind itself and slows everything else on that Mac. If you are reacting at any volume, use reaction.tapback.

Reading the outcome

Exactly one command event settles each reaction. There are four possible outcomes, and two of them are not failures even though they are not succeeded:

OutcomeSettles asWhat happened
appliedsucceededThe reaction was added
removedsucceededThe reaction was removed
already_reactedno_effectThat reaction was already there
not_reactedno_effectYou asked to remove one that was not there

no_effect means nothing changed because nothing needed to change. The end state of the conversation is exactly what you asked for, so treat it as success unless your application specifically cares whether it was the one that made the change — a counter, say, or an audit trail.

reaction.apply has only two outcomes, applied and already_reacted, with the same reading.

Genuine failures arrive as status: "failed" with an errorCode. Besides stale_target, the ones worth handling are tapback_unverified and reaction_unverified (the reaction may have been applied but could not be confirmed) and unsupported_capability (the Mac does not offer this reaction command, or not with the options you used). Treat any errorCode you do not recognise as a generic failure — the list is not closed. See Errors.

On this page