Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt

Use this file to discover all available pages before exploring further.

im.polls manages iMessage poll messages. A poll always belongs to a chat, so create(...) takes chat.guid. If you only have email addresses or phone numbers, create the chat first with im.chats.create(...). After a poll is created, store poll.pollMessageGuid. You need it to read the poll, vote, unvote, add options, and subscribe to events for that poll.

What You Can Do

NeedUse this when
Create a pollYou want to send a new poll message into a chat
Read stateYou need the latest title, options, and votes
VoteThe current account chooses an option or changes its choice
UnvoteThe current account removes its choice
Add an optionYou want to append another option to an existing poll
Subscribe to eventsYou need live poll creation, option, vote, and unvote changes

Two IDs

Poll APIs use two IDs. Keep them separate and the rest of the API is straightforward:
IDWhat it identifiesWhere it comes from
pollMessageGuidThe pollpoll.pollMessageGuid
optionIdentifierA poll optionpoll.options[*].optionIdentifier
The rule is simple:
  1. Pass pollMessageGuid to say which poll you are operating on.
  2. Pass optionIdentifier when you need to choose a specific option.
Do not pass option text to vote(...). The user may see "Pizza", but the API needs that option’s optionIdentifier.

Create a Poll

const poll = await im.polls.create(chat.guid, "What should we order?", [
  "Pizza",
  "Sushi",
  "Burgers",
]);

console.log(poll.pollMessageGuid);
The poll is sent to the chat as part of the same call. The return value is Poll:
{
  "pollMessageGuid": "poll-message-guid",  // Poll message GUID; used by later poll calls
  "chatGuid":        "any;+;group-id",     // Chat that contains the poll
  "title":           "Lunch?",             // Poll title
  "options": [                             // Current option list
    {
      "optionIdentifier": "option-1",      // Option ID; pass this to vote(...)
      "text":             "Pizza"          // Text shown to users
    }
  ],
  "votes": [                               // Current votes
    {
      "optionIdentifier": "option-1",      // Selected option ID
      "participant": {                     // Voter
        "address": "alice@example.com",
        "service": "iMessage"
      }
    }
  ]
}
InputRule
chatPass chat.guid, not an email address or phone number
titleTrimmed by the SDK/server; must not be empty after trimming
choicesAt least two strings; each choice is trimmed and must not be empty

Read and Modify

Get State

const latest = await im.polls.get(poll.pollMessageGuid);

console.log(latest.title, latest.options, latest.votes);
Returns the latest Poll. Use this when you need fresh option IDs or current vote state. Missing polls throw NotFoundError.

Vote

const option = poll.options[0];

const updated = await im.polls.vote(poll.pollMessageGuid, option.optionIdentifier);
Returns the updated Poll.
CaseResult
Current account has not votedRecords the selected option
Current account already votedReplaces the previous choice
pollMessageGuid does not existThrows NotFoundError
optionIdentifier is invalidThrows ValidationError

Unvote

const updated = await im.polls.unvote(poll.pollMessageGuid);
Returns the updated Poll. Missing polls throw NotFoundError.

Add an Option

const updated = await im.polls.addOption(poll.pollMessageGuid, "Thai");
Returns the updated Poll. The new option is appended to options.
InputRule
pollMessageGuidExisting poll message GUID
textTrimmed by the SDK/server; must not be empty after trimming
Missing polls throw NotFoundError.
create(...), vote(...), unvote(...), and addOption(...) accept optional { clientMessageId } for idempotent retries from your job system. Most direct calls can omit it. See error handling for details.

Poll Events

subscribeEvents(...) returns TypedEventStream<PollEvent>. Use the stream to observe changes made by other people, other devices, or another part of your system. Immediately after your code calls vote(...), unvote(...), or addOption(...), use that method’s returned Poll.

Scope

Only one poll:
const stream = im.polls.subscribeEvents({
  pollMessage: poll.pollMessageGuid,
});
All visible poll events:
const stream = im.polls.subscribeEvents();

Outer Event

Every poll event has type: "poll.changed". The outer fields answer which poll changed, which chat it belongs to, who triggered it, and when:
{
  "type":            "poll.changed",          // Fixed value for poll change events
  "pollMessageGuid": "poll-message-guid",     // Poll message GUID that changed
  "chatGuid":        "any;+;group-id",        // Chat that contains the poll
  "sequence":        123,                     // Event sequence for ordering and catch-up
  "isFromMe":        false,                   // Triggered by the current account
  "occurredAt":      "2026-01-01T12:00:00Z",  // Event time
  "actor": {                                  // Participant that triggered the event; may be absent
    "address": "alice@example.com",
    "service": "iMessage"
  },
  "delta": {                                  // The actual poll change
    "type": "voted",
    "optionIdentifier": "option-1"
  }
}

Delta Types

event.delta.type tells you what changed. Each type carries different fields:
event.delta.typeMeaningExtra fields
createdThe poll was createdtitle, options
optionAddedA new option was appendedtitle, options
votedA participant voted or changed their voteoptionIdentifier
unvotedA participant removed their voteoptionIdentifier
{
  "type":    "created",  // Poll was created
  "title":   "Lunch?",   // Current title
  "options": [           // Full option list
    {
      "optionIdentifier": "option-1",
      "text":             "Pizza"
    }
  ]
}

Handle Events

Switch on event.delta.type. When you subscribe to one poll, you do not need to check pollMessageGuid again:
for await (const event of im.polls.subscribeEvents({
  pollMessage: poll.pollMessageGuid,
})) {
  switch (event.delta.type) {
    case "created":
    case "optionAdded":
      console.log(event.delta.title, event.delta.options);
      break;

    case "voted":
    case "unvoted":
      console.log(event.delta.optionIdentifier);
      break;
  }
}
When you subscribe to all visible polls, use event.pollMessageGuid to identify the poll:
for await (const event of im.polls.subscribeEvents()) {
  console.log(event.pollMessageGuid, event.delta.type);
}
If the stream disconnects, use events to catch up on missed durable events, then continue consuming the live stream.

Next Steps

  1. Chats — create a chat and get chat.guid
  2. Messages — understand how poll messages appear in the message stream
  3. Events — catch up on durable events after a disconnect
  4. Error Handling — handle NotFoundError, ValidationError, and idempotent retries