> ## Documentation Index
> Fetch the complete documentation index at: https://developer.dizplai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cast a vote

> Casts a fan's vote for one of a poll's options. A fan has one vote in each poll, whichever collection they found it in, and only their first vote counts. Send a new Idempotency-Key with each vote, so a retry is never counted twice.



## OpenAPI

````yaml /experiences/api-reference/experiences.openapi.yaml post /polls/{poll_id}/votes
openapi: 3.1.0
info:
  title: 'Dizplai API: Experiences'
  version: 1.0.0
  summary: Read polls, Fan Voices and collections, and let fans vote and send messages.
  description: >
    Show polls and Fan Voices in your own apps and sites, and let fans take
    part: read experiences and collections, send

    fans' votes and messages, and read back a fan's own vote and messages.


    **Headers.** Send `x-api-key`, your organisation's API key, on every
    request. When a fan takes part, or reads

    their own vote or messages, add their token from your own login as
    `Authorization: Bearer <token>`.


    **Names.** An experience is one thing fans take part in: a poll or a Fan
    Voice. A collection groups experiences for a

    match or show, and an experience can be in many collections. Every ID is a
    UUID: store it in a UUID column or as

    text, and never parse it.


    **Conventions.** snake_case fields; `after` and `limit` paging (50 by
    default, 100 at most) with `{items, next}`;

    errors are RFC 9457 problems with a stable `code` and a `request_id`, and
    every response carries `x-request-id`.

    Types, statuses and codes can gain values within v1, so ignore one you don't
    know.


    **Refreshing.** Read an open poll every 10 seconds and lists every 30
    seconds, and wait as long as `Retry-After`

    says after a `429` or `503`. The Experiences guide has the details.
  contact:
    name: Dizplai
servers:
  - url: https://api.dizplai.com/v1
    description: The Dizplai API.
security:
  - apiKey: []
tags:
  - name: Experiences
    description: >-
      Everything fans can take part in, in one list: build a match or show
      screen from it, drawing each experience by its type.
  - name: Collections
    description: >-
      Groups of experiences, one for each match or show, to organise your
      screens. An experience can be in many collections, in its own order in
      each.
  - name: Polls
    description: >-
      A poll asks fans one question with a short list of answers. Read polls and
      their results, send a fan's vote, and read back the vote they made.
  - name: Fan Voices
    description: >-
      A Fan Voice asks fans to reply in their own words, with text, a voice note
      or a photo. Read a Fan Voice, send a fan's message, and show the fan what
      happened to it. Fans never see each other's messages.
paths:
  /polls/{poll_id}/votes:
    parameters:
      - name: poll_id
        in: path
        required: true
        description: The poll's ID, from a list of experiences or polls.
        schema:
          $ref: '#/components/schemas/PollId'
    post:
      tags:
        - Polls
      summary: Cast a vote
      description: >-
        Casts a fan's vote for one of a poll's options. A fan has one vote in
        each poll, whichever collection they found it in, and only their first
        vote counts. Send a new Idempotency-Key with each vote, so a retry is
        never counted twice.
      operationId: castPollVote
      parameters:
        - $ref: '#/components/parameters/VoteIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PollVote'
            examples:
              example:
                $ref: '#/components/examples/PollVote'
      responses:
        '202':
          description: >-
            Received, not yet counted, with a snapshot of the poll's results
            that includes the vote. Show the snapshot straight away as a static
            view: it doesn't update, and a read straight after the vote may not
            include it yet. For live results, keep reading the poll. A later
            vote from the same fan also answers 202, with its own snapshot, but
            is never counted, so retrying after a lost response, with the same
            Idempotency-Key, is safe. To show the fan's choice on any device,
            use Get the fan's vote. If the poll closes before a retry, the retry
            answers not_open, and Get the fan's vote shows whether the first
            attempt counted.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PollVoteReceived'
              examples:
                visible:
                  $ref: '#/components/examples/PollVoteReceived'
                withheld:
                  $ref: '#/components/examples/PollVoteReceivedWithheld'
        '400':
          description: >-
            The body is invalid or has no Idempotency-Key (invalid_request), the
            option isn't one this experience has (unknown_option), or it isn't
            taking votes (not_open).
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                invalid_request:
                  summary: invalid_request
                  value:
                    type: https://api.dizplai.com/problems/invalid-request
                    code: invalid_request
                    title: Invalid request
                    status: 400
                    detail: option_id is required.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                unknown_option:
                  summary: unknown_option
                  value:
                    type: https://api.dizplai.com/problems/unknown-option
                    code: unknown_option
                    title: Unknown option
                    status: 400
                    detail: >-
                      01a1f97a-b538-7f26-8efa-04686cf52e39 isn't one of this
                      poll's options.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                not_open:
                  summary: not_open
                  value:
                    type: https://api.dizplai.com/problems/not-open
                    code: not_open
                    title: Not open
                    status: 400
                    detail: 'Votes have stopped: this poll is closed.'
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
        '401':
          $ref: '#/components/responses/FanUnauthorised'
        '403':
          description: >-
            A guest on an experience that needs a signed-in fan
            (sign_in_required). Retrying won't help.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                sign_in_required:
                  summary: sign_in_required
                  value:
                    type: https://api.dizplai.com/problems/sign-in-required
                    code: sign_in_required
                    title: Sign-in required
                    status: 403
                    detail: This poll needs a signed-in fan.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
        '404':
          $ref: '#/components/responses/PollNotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/Unavailable'
      security:
        - apiKey: []
          fanToken: []
components:
  schemas:
    PollId:
      type: string
      description: A poll's ID, a UUID.
      format: uuid
      examples:
        - 0192fa7b-8d41-7bbc-9b58-02311fc6ef8e
    PollVote:
      type: object
      required:
        - option_id
      additionalProperties: false
      description: A fan's vote, the option they chose.
      properties:
        option_id:
          $ref: '#/components/schemas/OptionId'
          description: One of the poll's options.
      title: Poll vote
    PollVoteReceived:
      title: Vote received
      type: object
      required:
        - option_id
        - results
      description: >-
        The vote, received but not yet counted, with a snapshot of the poll's
        results that includes it.
      properties:
        option_id:
          $ref: '#/components/schemas/OptionId'
          description: The option the fan voted for, as sent.
        results:
          $ref: '#/components/schemas/PollResults'
          description: >-
            The poll's latest results with this vote added, for a static view
            straight after the vote. It doesn't update: for live results, keep
            reading the poll. withheld when fans may not see results yet, as
            results_visibility says.
    Problem:
      type: object
      required:
        - type
        - code
        - title
        - status
        - request_id
      description: >-
        What went wrong, in the same shape for every error: an RFC 9457 problem
        with a stable code. Act on code, which never changes meaning within a
        version, and on the HTTP status for a code you don't know. Quote
        request_id to support.
      properties:
        type:
          type: string
          format: uri-reference
          description: >-
            One URL per code: https://api.dizplai.com/problems/ followed by the
            code with hyphens, such as
            https://api.dizplai.com/problems/unknown-option for unknown_option.
        code:
          type: string
          x-extensible-enum:
            - invalid_request
            - unknown_option
            - not_open
            - media_not_accepted
            - invalid_upload
            - invalid_api_key
            - invalid_fan_token
            - forbidden
            - fan_blocked
            - sign_in_required
            - message_limit_reached
            - not_found
            - idempotency_conflict
            - rate_limited
            - unavailable
          description: >
            More codes may be added: for one you don't know, act on the HTTP
            status.


            | code | Status | Retry? | Meaning |

            |---|---|---|---|

            | invalid_request | 400 | No | The body or a parameter is invalid,
            or a required header such as Idempotency-Key is missing. |

            | unknown_option | 400 | No; read the poll again | An option ID the
            poll doesn't have. |

            | not_open | 400 | No; read the experience again | The experience
            isn't taking votes or messages: it isn't live yet, or votes have
            stopped. |

            | media_not_accepted | 400 | No | The Fan Voice doesn't take this
            kind of message, or the file's type or size is outside the limits. |

            | invalid_upload | 400 | No; upload again | The upload_id is
            unknown, already used or another fan's, or the file was never
            uploaded or its type or size differs from what you asked for. |

            | invalid_api_key | 401 | No | A missing or invalid x-api-key. |

            | invalid_fan_token | 401 | Once, with a fresh token | A missing,
            invalid or expired fan token, or one from another organisation's
            login. It carries WWW-Authenticate. |

            | forbidden | 403 | No | Your API key isn't allowed to make this
            request. |

            | fan_blocked | 403 | No | The fan is blocked from this Fan Voice. |

            | sign_in_required | 403 | No; ask the fan to sign in | A guest on
            an experience that needs a signed-in fan, or a guest's voice note or
            photo. |

            | message_limit_reached | 403 | No | The fan has used up this Fan
            Voice's messages of this kind. |

            | not_found | 404 | No | Not found, or not yours. |

            | idempotency_conflict | 409 | No | An Idempotency-Key reused with a
            different body while it's kept: 24 hours, or 5 minutes for an
            upload. |

            | rate_limited | 429 | After Retry-After | Too many requests. |

            | unavailable | 503 | After Retry-After | Votes or messages can't be
            taken right now. |
        title:
          type: string
          description: A short title for the code, such as Not open. It doesn't change.
        status:
          type: integer
          description: The HTTP status code.
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: >-
            What went wrong, in plain English. It can change, so don't act on
            it.
        request_id:
          type: string
          description: Matches the x-request-id response header.
        errors:
          type: array
          description: Field-level validation errors, with invalid_request.
          items:
            type: object
            required:
              - pointer
              - message
            properties:
              pointer:
                type: string
                description: A JSON Pointer to the field.
              message:
                type: string
                description: What's wrong with that field.
    OptionId:
      type: string
      description: >-
        An option's ID, what a fan's vote sends. Created with the option,
        opaque, and never reused, even if the option is removed before the poll
        goes live. Correcting an option's label keeps its ID.
      format: uuid
      examples:
        - 01a1f97a-b538-778f-bad7-0c1c35ff701c
    PollResults:
      description: >-
        What to show for the poll's results, by state. withheld: fans may not
        see them yet, as results_visibility says, and nothing else is sent.
        live: the current counts. final: voting has stopped and these are the
        confirmed totals, which can differ from the last live numbers, and go
        back to live if the poll opens again. Counts are never null: 0 means no
        votes. More states may be added: treat a state you don't know as
        withheld: show no counts.
      type: object
      required:
        - state
      properties:
        state:
          type: string
          x-extensible-enum:
            - withheld
            - live
            - final
          description: >-
            What to show now. Treat a state you don't know as withheld: show no
            counts.
      oneOf:
        - $ref: '#/components/schemas/WithheldResults'
        - title: Counted
          type: object
          required:
            - state
            - total_votes
            - options
          properties:
            state:
              type: string
              enum:
                - live
                - final
              description: >-
                live: the current counts, while votes are open. final: voting
                has stopped and these are the confirmed totals, which can differ
                from the last live numbers. If voting opens again, they go back
                to live.
            total_votes:
              type: integer
              minimum: 0
              description: 'Fans who voted. Never null: 0 means no votes.'
            options:
              type: array
              description: >-
                Every option, in the poll's order. Match option_id to the poll's
                options[].id for its label.
              items:
                type: object
                required:
                  - option_id
                  - votes
                  - percent
                properties:
                  option_id:
                    $ref: '#/components/schemas/OptionId'
                  votes:
                    $ref: '#/components/schemas/Votes'
                  percent:
                    $ref: '#/components/schemas/Percent'
        - title: Another state
          type: object
          required:
            - state
          description: 'A state added later. Treat it as withheld: show no counts.'
          properties:
            state:
              type: string
              not:
                enum:
                  - withheld
                  - live
                  - final
    WithheldResults:
      title: Withheld
      type: object
      required:
        - state
      description: >-
        Results fans may not see yet, as results_visibility says: only the state
        is sent, so show no counts.
      properties:
        state:
          type: string
          enum:
            - withheld
    Votes:
      type: integer
      minimum: 0
      description: 'Votes counted. Never null: 0 means no votes.'
    Percent:
      type: number
      minimum: 0
      maximum: 100
      description: >-
        Of total_votes, 0 to 100, rounded to one decimal place, so they may not
        add up to exactly 100.
  parameters:
    VoteIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        format: uuid
      example: 6d4e2a91-3c7b-4f05-8e1a-b29c5d7f0e63
      description: >-
        Required. A new UUID for each vote. If the connection drops, send the
        vote again with the same key: it's never counted twice.
  examples:
    PollVote:
      value:
        option_id: 01a1f97a-b538-778f-bad7-0c1c35ff701c
    PollVoteReceived:
      summary: Results fans may see, with the vote added
      value:
        option_id: 01a1f97a-b538-778f-bad7-0c1c35ff701c
        results:
          state: live
          total_votes: 4211
          options:
            - option_id: 01a1f97a-b538-778f-bad7-0c1c35ff701c
              votes: 2106
              percent: 50
            - option_id: 01a1f97a-b5a7-7b5b-9458-203a658c1e8d
              votes: 1263
              percent: 30
            - option_id: 01a1f97a-b5cc-7fcf-989c-56ebe53808c5
              votes: 842
              percent: 20
    PollVoteReceivedWithheld:
      summary: Results withheld until votes stop
      value:
        option_id: 01a1f97a-b538-778f-bad7-0c1c35ff701c
        results:
          state: withheld
  headers:
    RequestId:
      description: The request's ID. Quote it to support.
      schema:
        type: string
    WwwAuthenticate:
      description: >-
        With invalid_fan_token only: Bearer error="invalid_token", with an
        error_description.
      schema:
        type: string
    RetryAfter:
      description: >-
        How long to wait before retrying, as a whole number of seconds. Sent
        with every 429 and 503.
      schema:
        type: integer
        minimum: 0
  responses:
    FanUnauthorised:
      description: >-
        A missing or invalid x-api-key (invalid_api_key), or a missing, invalid
        or expired fan token, or one from another organisation's login
        (invalid_fan_token). Only invalid_fan_token carries WWW-Authenticate:
        refresh the fan's token and retry once.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            invalid_fan_token:
              summary: invalid_fan_token
              value:
                type: https://api.dizplai.com/problems/invalid-fan-token
                code: invalid_fan_token
                title: Invalid fan token
                status: 401
                detail: The fan's token has expired.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
            invalid_api_key:
              summary: invalid_api_key
              value:
                type: https://api.dizplai.com/problems/invalid-api-key
                code: invalid_api_key
                title: Invalid API key
                status: 401
                detail: The x-api-key isn't recognised.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    PollNotFound:
      description: >-
        The poll doesn't exist, isn't yours, or was taken down or deleted
        (not_found).
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            not_found:
              summary: not_found
              value:
                type: https://api.dizplai.com/problems/not-found
                code: not_found
                title: Not found
                status: 404
                detail: No such poll.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    TooManyRequests:
      description: >-
        Too many requests (rate_limited). Wait the number of seconds in
        Retry-After, then retry.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            rate_limited:
              summary: rate_limited
              value:
                type: https://api.dizplai.com/problems/rate-limited
                code: rate_limited
                title: Rate limited
                status: 429
                detail: Too many requests. Retry after 2 seconds.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    Unavailable:
      description: >-
        Votes or messages can't be taken right now (unavailable). Wait the
        number of seconds in Retry-After, then retry.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            unavailable:
              summary: unavailable
              value:
                type: https://api.dizplai.com/problems/unavailable
                code: unavailable
                title: Unavailable
                status: 503
                detail: Votes can't be taken right now. Retry after 5 seconds.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Your organisation's API key, supplied by Dizplai. Send it on every
        request. It isn't a secret, so it can ship in your app: what a fan does
        is protected by the fan's token.
    fanToken:
      type: http
      scheme: bearer
      description: >-
        The fan's token from your own login, sent as `Authorization: Bearer
        <token>`: a signed-in fan's or a guest's. Dizplai checks it against your
        login's details, agreed when you're set up, and a guest's token carries
        the guest claim agreed at the same time. Needed to vote, upload, send a
        message, and read the fan's own vote or messages.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.