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

# Submit Vote

> Submit a vote for a pair comparison.

Security / integrity rules (per plan):
- Votes require Supabase JWT (API keys are rejected)
- Votes are bound to the currently served sticky pair via pair_id
- winner_id must be one of the pair's samples
- For anchored experiments: both samples must share the same anchor_value
- Soft-deleted samples cannot be voted on



## OpenAPI

````yaml /openapi.json post /v1/experiments/{experiment_id}/votes
openapi: 3.1.0
info:
  description: Customer-owned evaluation lifecycle API.
  title: HeyBee Public API
  version: 1.0.0
servers:
  - description: Production
    url: https://api.heybee.app
security: []
paths:
  /v1/experiments/{experiment_id}/votes:
    post:
      tags:
        - Voting
      summary: Submit Vote
      description: >-
        Submit a vote for a pair comparison.


        Security / integrity rules (per plan):

        - Votes require Supabase JWT (API keys are rejected)

        - Votes are bound to the currently served sticky pair via pair_id

        - winner_id must be one of the pair's samples

        - For anchored experiments: both samples must share the same
        anchor_value

        - Soft-deleted samples cannot be voted on
      operationId: submit_vote_v1_experiments__experiment_id__votes_post
      parameters:
        - in: path
          name: experiment_id
          required: true
          schema:
            format: uuid
            title: Experiment Id
            type: string
        - in: header
          name: authorization
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: Authorization
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoteCreate'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoteResponse'
          description: Successful Response
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
          description: Validation Error
      security:
        - BearerAuth: []
components:
  schemas:
    VoteCreate:
      additionalProperties: false
      anyOf:
        - required:
            - winner_id
        - required:
            - outcome
        - required:
            - axis_outcomes
      description: |-
        Request to submit a vote.

        Votes are bound to the currently served sticky pair via pair_id.
      example:
        client_metric_version: v1
        feedback: First one has better coherence
        pair_id: 3a2f0c9a-8c0e-4c83-9c2d-5b7b6a0c4f12
        sample_a_playback_percent: 72.4
        sample_b_playback_percent: 95
        time_taken_ms: 4500
        winner_id: 550e8400-e29b-41d4-a716-446655440000
      properties:
        axis_outcomes:
          anyOf:
            - items:
                $ref: '#/components/schemas/AxisOutcome'
              type: array
            - type: 'null'
          description: >-
            Per-criterion outcomes for a multi-axis experiment. Mutually
            exclusive with the scalar outcome/winner_id (which are sugar for the
            single 'default' axis). The route layer enforces that all experiment
            axes are present; tie/cannot_tell count as answering.
          title: Axis Outcomes
        client_metric_version:
          anyOf:
            - maxLength: 64
              type: string
            - type: 'null'
          description: Client-side telemetry schema version
          title: Client Metric Version
        feedback:
          anyOf:
            - maxLength: 2000
              type: string
            - type: 'null'
          description: Optional feedback text explaining the choice
          title: Feedback
        outcome:
          anyOf:
            - enum:
                - left_wins
                - right_wins
                - tie
                - both_bad
                - cannot_tell
              type: string
            - type: 'null'
          description: >-
            Vote outcome. Required when winner_id is omitted. Non-choice
            outcomes do not count for ranking or spend.
          title: Outcome
        pair_id:
          description: Pair ID returned by GET next-pair (sticky)
          format: uuid
          title: Pair Id
          type: string
        sample_a_playback_percent:
          anyOf:
            - maximum: 100
              minimum: 0
              type: number
            - type: 'null'
          description: Playback percentage observed on sample A (audio flows)
          title: Sample A Playback Percent
        sample_a_playback_seconds:
          anyOf:
            - minimum: 0
              type: number
            - type: 'null'
          description: Playback seconds observed on sample A (audio flows)
          title: Sample A Playback Seconds
        sample_b_playback_percent:
          anyOf:
            - maximum: 100
              minimum: 0
              type: number
            - type: 'null'
          description: Playback percentage observed on sample B (audio flows)
          title: Sample B Playback Percent
        sample_b_playback_seconds:
          anyOf:
            - minimum: 0
              type: number
            - type: 'null'
          description: Playback seconds observed on sample B (audio flows)
          title: Sample B Playback Seconds
        sample_scores:
          anyOf:
            - items:
                $ref: '#/components/schemas/VoteSampleScore'
              type: array
            - type: 'null'
          description: >-
            Absolute per-sample scores for numeric axes in a mixed multi-axis
            vote.
          title: Sample Scores
        time_taken_ms:
          anyOf:
            - minimum: 0
              type: integer
            - type: 'null'
          description: Time taken to make the decision in milliseconds
          title: Time Taken Ms
        winner_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          description: Winning sample id for left_wins/right_wins outcomes.
          title: Winner Id
      required:
        - pair_id
      title: VoteCreate
      type: object
    VoteResponse:
      description: Response after submitting a vote.
      properties:
        bot_probability:
          anyOf:
            - type: number
            - type: 'null'
          title: Bot Probability
        comparisons_spent:
          default: 0
          title: Comparisons Spent
          type: integer
        counted_for_ranking:
          default: true
          title: Counted For Ranking
          type: boolean
        counted_for_spend:
          default: true
          title: Counted For Spend
          type: boolean
        created_at:
          format: date-time
          title: Created At
          type: string
        credit_cost:
          default: 0
          title: Credit Cost
          type: integer
        effective_reliability:
          anyOf:
            - type: number
            - type: 'null'
          title: Effective Reliability
        experiment_id:
          format: uuid
          title: Experiment Id
          type: string
        feedback:
          anyOf:
            - type: string
            - type: 'null'
          title: Feedback
        id:
          format: uuid
          title: Id
          type: string
        is_exam:
          default: false
          title: Is Exam
          type: boolean
        new_thoughts_balance:
          anyOf:
            - type: integer
            - type: 'null'
          title: New Thoughts Balance
        next_pair:
          anyOf:
            - $ref: '#/components/schemas/NextPairResponse'
            - type: 'null'
        next_pair_state:
          default: error
          enum:
            - ready
            - completed
            - paused
            - no_comparisons
            - no_pairs_available
            - blocked
            - error
          title: Next Pair State
          type: string
        outcome:
          default: left_wins
          title: Outcome
          type: string
        pair_id:
          format: uuid
          title: Pair Id
          type: string
        progress:
          anyOf:
            - $ref: '#/components/schemas/VoteProgressResponse'
            - type: 'null'
        remaining_comparisons:
          default: 0
          title: Remaining Comparisons
          type: integer
        sample_a_id:
          format: uuid
          title: Sample A Id
          type: string
        sample_b_id:
          format: uuid
          title: Sample B Id
          type: string
        selection_score:
          anyOf:
            - type: number
            - type: 'null'
          title: Selection Score
        thoughts_earned:
          default: 0
          title: Thoughts Earned
          type: integer
        time_taken_ms:
          anyOf:
            - type: integer
            - type: 'null'
          title: Time Taken Ms
        vote_cost_credits:
          default: 1
          title: Vote Cost Credits
          type: integer
        voter_id:
          format: uuid
          title: Voter Id
          type: string
        winner_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Winner Id
      required:
        - id
        - experiment_id
        - voter_id
        - pair_id
        - sample_a_id
        - sample_b_id
        - winner_id
        - created_at
      title: VoteResponse
      type: object
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          title: Detail
          type: array
      title: HTTPValidationError
      type: object
    AxisOutcome:
      additionalProperties: false
      description: One per-criterion judgment within a multi-axis comparison.
      properties:
        axis_key:
          description: Criterion key, e.g. 'motion'.
          maxLength: 64
          minLength: 1
          title: Axis Key
          type: string
        numeric_value:
          anyOf:
            - type: number
            - type: 'null'
          description: Numeric score for calibrated numeric exam criteria.
          title: Numeric Value
        outcome:
          description: >-
            Outcome for this axis. Non-choice outcomes do not count for ranking
            or spend.
          enum:
            - left_wins
            - right_wins
            - tie
            - both_bad
            - cannot_tell
          title: Outcome
          type: string
        winner_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          description: Winning sample id; only for left_wins/right_wins.
          title: Winner Id
      required:
        - axis_key
        - outcome
      title: AxisOutcome
      type: object
    VoteSampleScore:
      additionalProperties: false
      properties:
        axis_key:
          maxLength: 64
          minLength: 1
          title: Axis Key
          type: string
        sample_id:
          format: uuid
          title: Sample Id
          type: string
        score:
          title: Score
          type: number
      required:
        - sample_id
        - axis_key
        - score
      title: VoteSampleScore
      type: object
    NextPairResponse:
      description: Response with the next pair to compare.
      properties:
        allow_playback_speed:
          default: false
          description: Whether audio voters may adjust playback speed
          title: Allow Playback Speed
          type: boolean
        anchor:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          description: Structured anchor context for display
          title: Anchor
        anchor_value:
          anyOf:
            - type: string
            - type: 'null'
          description: The anchor value both samples share (if anchored experiment)
          title: Anchor Value
        axes:
          anyOf:
            - items:
                additionalProperties: true
                type: object
              type: array
            - type: 'null'
          description: >-
            Voting axes [{axis_key,label,description}] for a multi-axis
            experiment; None/empty => single default A/B.
          title: Axes
        eligibility_policy_version:
          anyOf:
            - type: string
            - type: 'null'
          description: Eligibility policy version used to validate this pair
          title: Eligibility Policy Version
        enforce_playback:
          default: false
          description: Whether both-output playback thresholds are enforced for audio votes
          title: Enforce Playback
          type: boolean
        is_exam:
          default: false
          description: True if this pair is an exam pair (quality control)
          title: Is Exam
          type: boolean
        login_required:
          default: false
          description: >-
            True when the current participant is linked to an account and must
            log in.
          title: Login Required
          type: boolean
        min_playback_percent:
          default: 0
          description: Required playback percentage for each audio output
          maximum: 100
          minimum: 0
          title: Min Playback Percent
          type: integer
        min_viewing_seconds:
          default: 0
          description: Minimum viewing time (seconds) before voting is accepted
          minimum: 0
          title: Min Viewing Seconds
          type: integer
        pair_id:
          description: Current sticky pair ID (required for vote binding)
          format: uuid
          title: Pair Id
          type: string
        participant_label:
          anyOf:
            - type: string
            - type: 'null'
          description: Short non-sensitive anonymous participant label.
          title: Participant Label
        participant_mode:
          anyOf:
            - enum:
                - anonymous
                - transferred
                - authenticated
                - linked_requires_login
              type: string
            - type: 'null'
          description: Current public voting participant mode.
          title: Participant Mode
        participant_required:
          default: false
          description: >-
            True when an anonymous voter must choose start-new or continue
            before a pair can be served.
          title: Participant Required
          type: boolean
        prefetch:
          anyOf:
            - items:
                additionalProperties: true
                type: object
              type: array
            - type: 'null'
          description: Optional hydrated upcoming pairs for client-side media warming
          title: Prefetch
        remaining_comparisons:
          default: 0
          description: Remaining comparisons in the experiment pool
          minimum: 0
          title: Remaining Comparisons
          type: integer
        require_feedback:
          default: true
          description: Whether vote feedback is required for this experiment
          title: Require Feedback
          type: boolean
        sample_a:
          additionalProperties: true
          description: First sample to compare
          title: Sample A
          type: object
        sample_b:
          additionalProperties: true
          description: Second sample to compare
          title: Sample B
          type: object
        selection_components:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          description: Optional pair-selection diagnostics
          title: Selection Components
        selection_policy:
          anyOf:
            - type: string
            - type: 'null'
          description: Pair selection policy identifier used for this pair
          title: Selection Policy
        selection_score:
          anyOf:
            - type: number
            - type: 'null'
          description: Selection utility score for this pair
          title: Selection Score
        submit_ready:
          default: true
          description: True when this pair ID is already accepted by vote submission.
          title: Submit Ready
          type: boolean
        vote_cost_credits:
          default: 1
          description: Credits required to vote this pair (0 for exam)
          minimum: 0
          title: Vote Cost Credits
          type: integer
      required:
        - sample_a
        - sample_b
        - pair_id
      title: NextPairResponse
      type: object
    VoteProgressResponse:
      description: Updated voting progress after a submitted vote.
      properties:
        percent_complete:
          default: 0
          title: Percent Complete
          type: integer
        remaining_pairs:
          default: 0
          title: Remaining Pairs
          type: integer
        total_pairs:
          default: 0
          title: Total Pairs
          type: integer
        voted_pairs:
          default: 0
          title: Voted Pairs
          type: integer
      title: VoteProgressResponse
      type: object
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          title: Location
          type: array
        msg:
          title: Message
          type: string
        type:
          title: Error Type
          type: string
      required:
        - loc
        - msg
        - type
      title: ValidationError
      type: object
  securitySchemes:
    BearerAuth:
      bearerFormat: Supabase JWT
      scheme: bearer
      type: http

````