poll.vote
Vote on a poll in a conversation, naming the poll and the choice by the ids its poll.created event carried. A vote adds a choice; it never takes one back.
POST /v1/commands/poll.voteVotes for one choice of a poll in a conversation on an iMessage line — a poll
the line sent or one a contact sent. Name the poll and the choice by the ids
the poll's poll.created event carried, on a stream opened with
?events=polls: a choice's id arrives nowhere else. See the
poll frame.
A vote adds its choice to the line's selection, because an iMessage poll lets each person pick several. Voting for another choice does not take the first one back: vote for Ramen, then for Pho, and the line holds both. Taking the line's vote back is not available yet.
Honours Idempotency-Key.
Target
Prop
Type
Payload
Prop
Type
What is refused before anything is sent
Each is 422 validation_failed, and nothing reaches the line:
| Field | When |
|---|---|
target.conversationId | The conversation is not one the transport has named |
payload.pollExternalId | Missing, blank, or not an id of the form a poll's id takes |
payload.optionExternalId | Missing, blank, or not an id of the form a choice's id takes |
payload.remove | Present at all: taking the line's vote back is not available yet |
payload.<key> | Any other key — poll.vote takes the two ids above and no others |
Request
curl https://api.mapier.ai/v1/commands/poll.vote \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lunch-poll-vote-ramen" \
-d '{
"target": { "conversationId": "a0000000-0000-4000-8000-000000000001" },
"payload": {
"pollExternalId": "2F6B1C3D-8E4A-4B5C-9D7E-0A1B2C3D4E5F",
"optionExternalId": "B2C7E9A1-0D3F-4E6A-8B5C-7D9E1F2A3B4C"
}
}'Response
{ "commandId": "cmd-21", "status": "queued", "disposition": "queued" }Then a command frame, type: "poll.vote":
succeededonce the line voted. Apoll.votedwithactor: "line"follows on a stream opened with?events=polls, carrying the line's whole selection inselectedOptionExternalIds.failedwithstale_targetwhen the poll is not among the conversation's 30 newest messages, or with a capabilityerrorCode,failureClass: capability, on a line whose device cannot vote.ambiguouswitherrorCode: "poll_vote_unverified"when the line could not prove the vote.
{
"type": "poll.voted",
"conversationId": "a0000000-0000-4000-8000-000000000001",
"actor": "line",
"contactId": null,
"pollExternalId": "2F6B1C3D-8E4A-4B5C-9D7E-0A1B2C3D4E5F",
"selectedOptionExternalIds": [
"B2C7E9A1-0D3F-4E6A-8B5C-7D9E1F2A3B4C",
"C3D8F0B2-1E4A-4F7B-9C6D-8E0F2A3B4C5D"
],
"occurredAt": "2026-10-01T18:12:09.000Z",
"simulated": false
}poll_vote_unverified: read the poll before you vote again
The vote may or may not have been made. Wait for the poll's next poll.voted with actor: "line"
— its selectedOptionExternalIds is what the line holds — before voting again.
In test
A test line rehearses a vote on the poll it sent: the sandbox makes the vote,
and the poll.voted it produces carries "simulated": true. Nobody on the
sandbox phone creates polls or votes, so there is no contact's poll to vote on
in test. The refusals above are the same.
Errors
A command answers the one set of HTTP refusals
POST /v1/commands/{type} publishes, so that table
is the one to handle. What is particular to this type:
422 validation_failedattarget.conversationId,payload.pollExternalId,payload.optionExternalId,payload.removeorpayload.<key>, above.404 unsupported_commandwithreason: not_on_channelon a line of any other channel.
Settlement codes you will see for this command: stale_target,
poll_vote_unverified, and a capability code on a line that cannot vote. See
Error codes.