---
openapi: 3.0.3
info:
  title: HiFy APIs
  description: API for HiFy resources
  version: 1.0.0
paths:
  "/v1/admin/editorials":
    get:
      security:
      - UserToken: []
      tags:
      - Admin Editorials
      summary: List editorials
      description: 'Returns editorials newest first, paginated. Total counts are returned
        in the X-Total-Count and X-Total-Pages response headers.

        '
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
        description: Page number (1-based).
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
        description: Items per page.
      responses:
        '200':
          description: Editorials
          headers:
            X-Total-Count:
              schema:
                type: integer
              description: Total number of editorials.
            X-Total-Pages:
              schema:
                type: integer
              description: Total number of pages.
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Editorial"
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - caller is not an admin
    post:
      security:
      - UserToken: []
      tags:
      - Admin Editorials
      summary: Create an editorial
      description: 'Creates an editorial and, if a media array is supplied, its ordered
        set of assets.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/EditorialInput"
      responses:
        '201':
          description: Editorial created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Editorial"
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - caller is not an admin
        '422':
          description: Unprocessable entity - validation failed
  "/v1/admin/editorials/{id}":
    patch:
      security:
      - UserToken: []
      tags:
      - Admin Editorials
      summary: Update an editorial
      description: 'Updates an editorial''s attributes. If a media array is supplied
        it replaces the editorial''s existing assets rather than diffing them.

        '
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Editorial id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/EditorialInput"
      responses:
        '200':
          description: Editorial updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Editorial"
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - caller is not an admin
        '404':
          description: Editorial not found
        '422':
          description: Unprocessable entity - validation failed
    delete:
      security:
      - UserToken: []
      tags:
      - Admin Editorials
      summary: Delete an editorial
      description: Deletes an editorial and its media.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Editorial id.
      responses:
        '204':
          description: Editorial deleted
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - caller is not an admin
        '404':
          description: Editorial not found
  "/v1/admin/scoring_formats":
    get:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: List scoring formats
      description: 'Paginated. Total counts are returned in the X-Total-Count and
        X-Total-Pages response headers.

        '
      parameters:
      - name: sport
        in: query
        required: false
        schema:
          type: string
          enum:
          - padel
          - pickleball
      - name: active
        in: query
        required: false
        schema:
          type: boolean
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: Scoring formats
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/ScoringFormat"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
    post:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Create a scoring format
      description: 'Rules are validated by compiling them. A format the scoring engine
        cannot play out — anything but 1, 2 or 3 sets to win, or a single-set race
        to a length other than 8 or 9 — is refused here rather than mis-scoring a
        live match.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ScoringFormat"
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ScoringFormat"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '422':
          description: 'Rules do not compile, the sport is unknown or unscoreable,
            or the slug is taken for this sport

            '
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/admin/scoring_formats/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Get a scoring format
      responses:
        '200':
          description: Scoring format
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ScoringFormat"
        '404':
          description: Not found
    patch:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Update a scoring format
      description: 'A rules change is refused while any non-pending match is scored
        under the format, directly or through its tournament — create a new format
        instead. Display fields (name, subtitle, description, real_world_use, display_overrides,
        position, active) stay editable at all times.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ScoringFormat"
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ScoringFormat"
        '422':
          description: Rules do not compile, or are frozen by an in-flight match
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    delete:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Delete or retire a scoring format
      description: 'Deleted outright when nothing references it. A format a tournament
        or match points at is deactivated instead, so those keep resolving and a played
        match''s snapshot stays traceable to a row.

        '
      responses:
        '204':
          description: Deleted
        '200':
          description: Referenced, so retired instead
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/ScoringFormat"
                - type: object
                  properties:
                    retired:
                      type: boolean
                    reason:
                      type: string
  "/v1/admin/scoring_formats/preview":
    post:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Compile unsaved rules
      description: 'Returns the snapshot a device would be given, without writing
        anything. This is how a format is checked before it is saved.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ScoringFormat"
      responses:
        '200':
          description: Compiled snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  scoring_config_snapshot:
                    type: object
                    description: >
                      When the format plays a different deuce on set or match point, the
                      snapshot carries a top-level deuce_overrides object keyed set_point /
                      match_point, each shaped like deuce. Absent otherwise.
        '422':
          description: Rules do not compile
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PreviewError"
  "/v1/admin/scoring_formats/vocabulary":
    get:
      security:
      - UserToken: []
      tags:
      - Admin Scoring Formats
      summary: Allowed rule vocabulary
      description: 'The rule keys and value sets a format may be built from, so an
        ops tool can render a form without another backend release.

        '
      responses:
        '200':
          description: Vocabulary
          content:
            application/json:
              schema:
                type: object
                properties:
                  sports:
                    type: array
                    items:
                      type: string
                  leaf_scoring:
                    type: array
                    items:
                      type: string
                  serve_models:
                    type: array
                    items:
                      type: string
                  deuce_max_advantages:
                    type: array
                    items:
                      type: integer
                      nullable: true
                  deuce_mechanisms:
                    type: object
                  deuce_override_triggers:
                    type: array
                    description: >
                      Games that may play a different deuce, authored in the leaf deuce
                      as on_set_point / on_match_point.
                    items:
                      type: string
                      enum: [set_point, match_point]
                  sets_to_win:
                    type: object
                  race_targets:
                    type: object
                  display_attributes:
                    type: array
                    items:
                      type: string
  "/v1/annotation_tasks":
    get:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: List annotation tasks
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: status
        in: query
        schema:
          type: string
      - name: stacking_status
        in: query
        description: Only tasks with a recording in this state. done means the
          task is ready to annotate.
        schema:
          type: string
          enum:
          - open
          - scheduled
          - done
          - archived
      - name: start_time
        in: query
        schema:
          type: string
      - name: end_time
        in: query
        schema:
          type: string
      - name: arena_id
        in: query
        schema:
          type: integer
      - name: arena_ids
        in: query
        description: Filter by multiple arena ids. Repeat the param using bracket
          notation, e.g. arena_ids[]=1&arena_ids[]=2.
        schema:
          type: array
          items:
            type: integer
      responses:
        '200':
          description: Successful response
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Create annotation task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                annotation_task:
                  type: object
                  properties:
                    arena_id:
                      type: integer
                    s3_path:
                      type: string
                    ingestion_slot:
                      type: string
                    s3_bucket:
                      type: string
                    status:
                      type: string
      responses:
        '201':
          description: Created
        '422':
          description: Unprocessable Entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/annotation_tasks/{id}":
    get:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Get annotation task
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  annotation_videos:
                    type: array
                    description: 'Every camera''s recording for this task: annotation
                      cameras first, then the rest, each group in camera order. Empty
                      uploads are left out. A play moment''s tick applies to every camera.'
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        camera_id:
                          type: integer
                        s3_bucket:
                          type: string
                        s3_path:
                          type: string
      consumes:
      - application/json
      produces:
      - application/json
    patch:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Update annotation task
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                annotation_task:
                  type: object
                  properties:
                    arena_id:
                      type: integer
                    s3_path:
                      type: string
                    ingestion_slot:
                      type: string
                    s3_bucket:
                      type: string
                    status:
                      type: string
      responses:
        '200':
          description: Successful response
        '422':
          description: Unprocessable Entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/annotation_tasks/{id}/clips":
    get:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Get clips for annotation task
      description: 'Returns one entry per packaged clip of the task''s play moments,
        ordered by tick and camera. The lowest-resolution variant is the preview and
        the highest-resolution variant is the download. Clips with no variants are
        omitted.

        '
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      - name: start_tick
        in: query
        description: Only clips whose play moment tick is at or after this tick
        schema:
          type: integer
      - name: end_tick
        in: query
        description: Only clips whose play moment tick is at or before this tick
        schema:
          type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    camera_id:
                      type: integer
                    occurred_at:
                      type: string
                      format: date-time
                    tick:
                      type: integer
                    s3_bucket:
                      type: string
                    preview_s3_path:
                      type: string
                      description: downloadable.mp4 of the lowest-resolution variant
                    preview_resolution:
                      type: string
                      example: 240p
                    downloadable_s3_path:
                      type: string
                      description: downloadable.mp4 of the highest-resolution variant
                    downloadable_resolution:
                      type: string
                      example: 1080p
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/annotation_tasks/{id}/next":
    get:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Get next annotation task
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                description: The next task, or empty when there is none.
                items:
                  type: object
                  properties:
                    annotation_videos:
                      type: array
                      description: 'Every camera''s recording for this task: annotation
                        cameras first, then the rest, each group in camera order. Empty
                        uploads are left out. A play moment''s tick applies to every camera.'
                      items:
                        type: object
                        properties:
                          id:
                            type: integer
                          camera_id:
                            type: integer
                          s3_bucket:
                            type: string
                          s3_path:
                            type: string
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/annotation_tasks/{id}/game_participants":
    get:
      security:
      - UserToken: []
      tags:
      - Annotation Tasks
      summary: Get game participants for annotation task
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      - name: filter_by_time_window
        in: query
        required: false
        schema:
          type: boolean
          default: false
        description: When true, returns only participants who appeared within 10 minutes
          of the ingestion time. When false or omitted, returns all participants from
          overlapping games.
      responses:
        '200':
          description: Successful response
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/arenas":
    get:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: List all arenas
      parameters:
      - name: facility_id
        in: query
        schema:
          type: integer
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of arenas
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
    post:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Create a new arena
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                arena:
                  type: object
                  properties:
                    facility_id:
                      type: integer
                    name:
                      type: string
                    enabled:
                      type: boolean
                    in_facility_id:
                      type: string
                    ip:
                      type: string
                    is_indoor:
                      type: boolean
                    has_floodlights:
                      type: boolean
                    players_per_game:
                      type: integer
                    sports_played:
                      type: array
                      items:
                        type: string
                    dimensions:
                      type: array
                      items:
                        type: number
      responses:
        '201':
          description: Arena created
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/arenas/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Get a specific arena
      responses:
        '200':
          description: Arena details
          content:
            application/json:
              schema:
                type: object
    put:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Update an arena
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                arena:
                  properties:
                    facility_id:
                      type: integer
                    name:
                      type: string
                    enabled:
                      type: boolean
                    in_facility_id:
                      type: string
                    ip:
                      type: string
                    is_indoor:
                      type: boolean
                    has_floodlights:
                      type: boolean
                    players_per_game:
                      type: integer
                    sports_played:
                      type: array
                      items:
                        type: string
                    dimensions:
                      type: array
                      items:
                        type: number
      responses:
        '200':
          description: Arena updated
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Delete an arena
      responses:
        '204':
          description: Arena deleted
  "/v1/arenas/bookings":
    get:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Get arena bookings
      parameters:
      - name: date
        in: query
        schema:
          type: string
          format: date
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of bookings
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  "/v1/arenas/{id}/games":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Arenas
      summary: Get games for an arena
      parameters:
      - name: date
        in: query
        schema:
          type: string
          format: date
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of games
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  "/v1/assets/upload-url":
    get:
      security:
      - UserToken: []
      tags:
      - Assets
      summary: Get upload URL
      operationId: getUploadUrl
      parameters:
      - name: kind
        in: query
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  upload_url:
                    type: string
                  object_name:
                    type: string
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/intelligence/callback":
    post:
      security:
      - UserToken: []
      tags:
      - Intelligence Callbacks
      summary: Process intelligence workflow callback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - job_id
              - workflow
              - results_s3_bucket
              - results_s3_path
              properties:
                job_id:
                  type: string
                  description: The ID of the intelligence job
                workflow:
                  type: string
                  description: The type of workflow being processed
                  example: player_identification
                results_s3_bucket:
                  type: string
                  description: S3 bucket containing the results
                results_s3_path:
                  type: string
                  description: S3 path to the results file
      responses:
        '200':
          description: Workflow processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
        '400':
          description: Invalid workflow specified
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: error
                  message:
                    type: string
                    example: 'Job abc123: Unknown workflow: unknown_workflow'
  "/v1/cameras":
    get:
      security:
      - UserToken: []
      tags:
      - Cameras
      summary: List all cameras
      parameters:
      - name: page
        in: query
        description: Page number
        required: false
        schema:
          type: integer
      - name: per_page
        in: query
        description: Number of items per page
        required: false
        schema:
          type: integer
      - name: arena_id
        in: query
        description: Arena ID that camera belongs to
        required: false
        schema:
          type: integer
      - name: in_arena_id
        in: query
        description: ID of the camera within the arena
        required: false
        schema:
          type: integer
      - name: usage_type
        in: query
        description: Camera usage type
        required: false
        schema:
          type: string
      operationId: listCameras
      responses:
        '200':
          description: List of cameras with arena and facility information
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    arena:
                      type: object
                      properties:
                        facility:
                          type: object
    post:
      security:
      - UserToken: []
      tags:
      - Cameras
      summary: Create a new camera
      operationId: createCamera
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                camera:
                  type: object
                  properties:
                    arena_id:
                      type: string
                    in_arena_id:
                      type: string
                    enabled:
                      type: boolean
                    noise_cancellation_enabled:
                      type: boolean
                      description: Opt this camera into measured audio noise cancellation
                        in the live encoder.
                    brand:
                      type: string
                    model:
                      type: string
                    arena_boundary_points:
                      type: array
                      items:
                        type: object
                    ip:
                      type: string
                    http_port:
                      type: integer
                    rtsp_port:
                      type: integer
                    usage_types:
                      type: array
                      items:
                        type: string
                    username:
                      type: string
                    password:
                      type: string
      responses:
        '201':
          description: Camera created successfully
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/cameras/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      security:
      - UserToken: []
      tags:
      - Cameras
      summary: Update a camera
      operationId: updateCamera
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                camera:
                  type: object
                  properties:
                    arena_id:
                      type: string
                    in_arena_id:
                      type: string
                    enabled:
                      type: boolean
                    noise_cancellation_enabled:
                      type: boolean
                      description: Opt this camera into measured audio noise cancellation
                        in the live encoder.
                    brand:
                      type: string
                    model:
                      type: string
                    arena_boundary_points:
                      type: array
                      items:
                        type: object
                    ip:
                      type: string
                    http_port:
                      type: integer
                    rtsp_port:
                      type: integer
                    usage_types:
                      type: array
                      items:
                        type: string
                    username:
                      type: string
                    password:
                      type: string
      responses:
        '200':
          description: Camera updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Camera"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    put:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                camera:
                  type: object
                  properties:
                    arena_id:
                      type: string
                    in_arena_id:
                      type: string
                    enabled:
                      type: boolean
                    noise_cancellation_enabled:
                      type: boolean
                      description: Opt this camera into measured audio noise cancellation
                        in the live encoder.
                    brand:
                      type: string
                    model:
                      type: string
                    arena_boundary_points:
                      type: array
                      items:
                        type: object
                    ip:
                      type: string
                    http_port:
                      type: integer
                    rtsp_port:
                      type: integer
                    usage_types:
                      type: array
                      items:
                        type: string
                    username:
                      type: string
                    password:
                      type: string
      responses:
        '200':
          description: Camera updated successfully
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Cameras
      summary: Delete a camera
      operationId: deleteCamera
      responses:
        '204':
          description: Camera deleted
  "/v1/circle/members/{memberId}":
    delete:
      security:
      - UserToken: []
      tags:
      - Circles
      summary: Remove a player from the caller's circle
      description: 'One-way: the player leaves the caller''s circle, but the caller
        stays in the player''s circle. Playing another game together adds them back.

        '
      parameters:
      - name: memberId
        in: path
        required: true
        description: UUID of the player to remove
        schema:
          type: string
          format: uuid
      responses:
        '204':
          description: Player removed from the circle
        '401':
          description: Missing or invalid user token
        '403':
          description: Caller is not a player
        '404':
          description: Player is not in the caller's circle
  "/v1/clips/{id}":
    get:
      security:
      - UserToken: []
      tags:
      - Clips
      summary: Show clip details
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/configs":
    get:
      security:
      - UserToken: []
      tags:
      - Configs
      summary: Get all configs
      operationId: getAllConfigs
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
      produces:
      - application/json
      consumes:
      - application/json
  "/v1/edge/mender/device-authentication/device-status-changed":
    post:
      tags:
      - Edge Mender
      summary: Receive Mender device status change webhook
      description: 'Called by Mender when a device''s authentication status changes
        (e.g. accepted, rejected). Requires a valid HMAC-SHA256 signature in X-MEN-Signature.
        No user authentication needed.

        '
      security: []
      parameters:
      - in: header
        name: X-MEN-Signature
        required: true
        schema:
          type: string
        description: HMAC-SHA256 signature of the raw request body, keyed with the
          Mender webhook secret
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - type
              - data
              properties:
                type:
                  type: string
                  example: device-status-changed
                data:
                  type: object
                  properties:
                    id:
                      type: string
                      description: Mender device ID
                    status:
                      type: string
                      enum:
                      - pending
                      - accepted
                      - rejected
                      - preauthorized
                      - noauth
                    auth_sets:
                      type: array
                      items:
                        type: object
                        properties:
                          iddata:
                            type: string
                            description: JSON string containing device identity data
                              (e.g. {"mac":"AA:BB:CC:DD:EE:FF"})
      responses:
        '200':
          description: Webhook processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  message:
                    type: string
                    example: Webhook processed successfully
        '400':
          description: Missing or invalid signature, or malformed JSON body
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: error
                  message:
                    type: string
                    example: Invalid request
  "/v1/edge/mender/device-preauthorizations":
    post:
      tags:
      - Edge Mender
      summary: Preauthorize a recorder device with Mender
      description: 'Called by recorder/scripts/preauthorize-mender-device.sh during
        bench provisioning, before the Mender client is started. Registers the device''s
        MAC and public key with Mender so it is accepted on first contact instead
        of waiting in "pending". Also records the device on the backend as "preauthorized".
        Authenticated as a recorder_provisioning partner integration; no user session.

        '
      security:
      - PartnerAccessKeyId: []
        PartnerSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - mac
              - pubkey
              properties:
                mac:
                  type: string
                  example: b8:27:eb:12:34:56
                  description: Device MAC, exactly as the Mender identity script reports
                    it
                pubkey:
                  type: string
                  description: PEM-encoded RSA public key derived from /var/lib/mender/mender-agent.pem
                  example: |
                    -----BEGIN PUBLIC KEY-----
                    MIIBIjANBg...
                    -----END PUBLIC KEY-----
      responses:
        '201':
          description: Device preauthorized with Mender
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
        '401':
          description: Missing, unknown, inactive or incorrectly signed credential
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_credential
                  message:
                    type: string
        '403':
          description: Valid credential, but not a recorder_provisioning integration
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: forbidden
                  message:
                    type: string
        '422':
          description: Malformed MAC address, or a public key that is unreadable or
            actually private
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_request
                  message:
                    type: string
        '409':
          description: The MAC or Mender device id is already registered to a different
            device
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: conflict
                  message:
                    type: string
        '502':
          description: Mender management API rejected the preauthorization
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: upstream_error
                  message:
                    type: string
  "/v1/edge/mender/software-releases":
    post:
      tags:
      - Edge Mender
      summary: Record a recorder artifact uploaded to Mender
      description: 'Called by the Mender Deployment workflow after a successful upload.
        Inserts the artifact with latest false. A retry of the same artifact name
        returns the existing row and does not change latest. Authenticated as a recorder_release
        partner integration; no user session.

        '
      security:
      - PartnerAccessKeyId: []
        PartnerSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - artifact_name
              - device_type
              properties:
                artifact_name:
                  type: string
                  example: recorder-24-09-26-abc1234
                device_type:
                  type: string
                  example: raspberrypi5
      responses:
        '201':
          description: Release recorded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EdgeSoftwareRelease"
        '200':
          description: Release was already recorded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EdgeSoftwareRelease"
        '401':
          description: Missing, unknown, inactive or incorrectly signed credential
        '403':
          description: Valid credential, but not a recorder_release integration
        '422':
          description: artifact_name is blank or device_type is not supported
  "/v1/edge/mender/software-releases/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      tags:
      - Edge Mender
      summary: Mark a recorded artifact as the one automated deploys ship
      description: 'Admin only. Sets latest on this row and clears it on the previous
        latest row for the same device type. latest must be true.

        '
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - latest
              properties:
                latest:
                  type: boolean
                  enum:
                  - true
      responses:
        '200':
          description: Release marked latest
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EdgeSoftwareRelease"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
        '422':
          description: latest was not true
  "/v1/edge/devices":
    get:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: List devices, newest first
      parameters:
      - name: mac_address
        in: query
        description: Filter devices by MAC address.
        schema:
          type: string
      - name: mender_device_id
        in: query
        description: Filter devices by Mender device ID.
        schema:
          type: string
      - name: facility_id
        in: query
        description: Filter devices by assigned facility.
        schema:
          type: integer
      - name: upload_mode
        in: query
        description: Filter devices by upload mode.
        schema:
          type: string
          enum:
          - continuous
          - on_demand
      - name: status
        in: query
        description: Filter by status. Decommissioned devices are listed only when
          this is set.
        schema:
          type: string
          enum:
          - pending
          - accepted
          - rejected
          - preauthorized
          - noauth
          - decommissioned
      - name: unmapped
        in: query
        description: When true, only devices with no facility.
        schema:
          type: boolean
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of devices
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Device"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
  "/v1/edge/devices/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Get a device
      responses:
        '200':
          description: Device details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Device"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
    patch:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Update a device
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - device
              properties:
                device:
                  type: object
                  properties:
                    facility_id:
                      type: integer
                      nullable: true
                    status:
                      type: string
                      enum:
                      - pending
                      - accepted
                      - rejected
                      - preauthorized
                      - noauth
                    upload_mode:
                      type: string
                      enum:
                      - continuous
                      - on_demand
                      description: on_demand uploads only footage a game claims. Pushes a new config to the device.
                    preview_generation_enabled:
                      type: boolean
                      description: Pushes a new config to the device.
                    archive_disk:
                      type: boolean
                      description: false for an SSD-only Pi with no archive HDD (sent as gateway.archive_enabled). Pushes a new config to the device.
      responses:
        '200':
          description: Device updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Device"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/edge/devices/{id}/assign-arenas":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Assign arenas to a device
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - device
              properties:
                device:
                  type: object
                  required:
                  - arena_ids
                  properties:
                    arena_ids:
                      type: array
                      description: Arena IDs to assign; replaces all existing assignments.
                        Pass [] to clear.
                      items:
                        type: integer
                    facility_id:
                      type: integer
                      description: 'Facility to assign alongside the arenas. Only
                        applied when the key is present; omit it to keep the device''s
                        current facility. The device must end up with a facility —
                        supply this unless the device already has one. Every arena
                        in arena_ids must belong to that facility.

                        '
      responses:
        '200':
          description: Arenas assigned; device tags synced to Mender
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Device"
        '400':
          description: Missing arena_ids key
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '422':
          description: 'Unprocessable entity – the device has no facility (none set
            and none provided), the device is not yet accepted by Mender (facility
            cannot be assigned until it is), or one or more arenas do not belong
            to the device''s facility.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
                    example: device.facility_id is required before assigning arenas
  "/v1/edge/devices/{id}/deployments":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Trigger a Mender deployment for a device
      description: 'Enqueues a Mender deployment for the device. For `type=config`
        a device config push is enqueued; for `type=code` an artifact deployment is
        enqueued and `artifact_name` is required.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - deployment
              properties:
                deployment:
                  type: object
                  required:
                  - type
                  properties:
                    type:
                      type: string
                      enum:
                      - code
                      - config
                      description: The kind of deployment to trigger.
                    artifact_name:
                      type: string
                      description: Mender artifact name; required when type is `code`.
      responses:
        '202':
          description: Deployment enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  enqueued:
                    type: boolean
                    example: true
                  type:
                    type: string
                    example: code
                  device_deployment_id:
                    type: integer
                    description: Row that will track this deployment's Mender status.
        '400':
          description: 'Bad request – type is missing/not one of code|config, or artifact_name
            is blank for type=code.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
        '409':
          description: Device is decommissioned
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
    get:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: List Mender deployments recorded for a device
      description: Returns deployments newest first, including status polled from
        Mender.
      responses:
        '200':
          description: Deployments for the device
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/DeviceDeployment"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
  "/v1/edge/devices/{id}/bootstrap":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Bootstrap a device with config and the latest artifact
      description: 'Enqueues a config deployment and a deployment of the artifact
        marked latest. Returns 422 when the device has no facility, no arena, or no
        release is marked latest.

        '
      responses:
        '202':
          description: Bootstrap deployments recorded
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/DeviceDeployment"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
        '409':
          description: Device is decommissioned
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
        '422':
          description: Device is not ready to bootstrap, or no release is marked latest
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
  "/v1/edge/devices/{id}/decommission":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Edge Devices
      summary: Decommission a device
      description: 'Retires the device immediately: removes its arenas and facility,
        revokes its API token, marks open footage requests lost and aborts in-flight
        deployments. A background job then deletes its Cloudflare tunnel and DNS record
        and decommissions it in Mender. Tailscale is not touched; remove the node
        manually. Calling this again while that cleanup is unfinished re-enqueues
        it.

        '
      responses:
        '202':
          description: Device decommissioned, or already decommissioned with cleanup unfinished; external cleanup enqueued
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Device"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
        '409':
          description: Device is already decommissioned and its cleanup has finished
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
        '422':
          description: The device could not be saved as decommissioned
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: string
  "/v1/admin/diagnostics/content_gap":
    get:
      security:
      - UserToken: []
      tags:
      - Admin Diagnostics
      summary: Diagnose late or missing footage for a game or arena window
      description: |
        Admin-only diagnostic that classifies when footage for a window arrived (`on_time`, `late`, `missing`) and names the most likely cause, using arena video metadata, previews, sibling arenas and recorder telemetry from Grafana Cloud Prometheus. Returns the same fields as one row of the `diagnostics:content_gaps` rake task CSV.

        Provide a scope in one of two mutually exclusive modes:
        - **Game mode**: pass `game_id`. The arena and time window are derived
          from the game.
        - **Arena window mode**: pass `arena_id`, `start_time` and `end_time`
          (all three required). `start_time` and `end_time` must be ISO8601
          timestamps, `end_time` must be after `start_time`, and the window
          may span at most 7 days.
      parameters:
      - name: game_id
        in: query
        required: false
        description: 'Game to diagnose. When present, the request is treated as game
          mode and `arena_id`/`start_time`/`end_time` are ignored.

          '
        schema:
          type: integer
      - name: arena_id
        in: query
        required: false
        description: Arena to diagnose. Required in arena window mode.
        schema:
          type: integer
      - name: start_time
        in: query
        required: false
        description: ISO8601 start of the window. Required in arena window mode.
        schema:
          type: string
          format: date-time
      - name: end_time
        in: query
        required: false
        description: 'ISO8601 end of the window; must be after start_time and within
          7 days of it. Required in arena window mode.

          '
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: Content gap diagnosis
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ContentGapDiagnostic"
        '401':
          description: Unauthorized – missing or invalid auth token
        '403':
          description: Forbidden – caller is not an admin
        '404':
          description: Game or arena not found
        '422':
          description: 'Unprocessable entity – scope could not be resolved (neither
            game_id nor a complete arena_id+start_time+end_time was provided), a
            timestamp was invalid, or the window exceeded 7 days.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Provide either game_id or arena_id+start_time+end_time
        '502':
          description: Grafana Cloud Prometheus could not be queried
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: 'Prometheus unreachable: execution expired'
  "/v1/explore":
    get:
      security:
      - UserToken: []
      tags:
      - Explore
      summary: Fetch a page of the Explore feed
      description: 'Returns the viewer''s ranked feed of clips, highlights and editorial
        posts. Paging is by opaque cursor; pass the previous response''s next_cursor
        to continue. Only the item envelope is described here — source carries a different
        shape per type.

        '
      parameters:
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: Opaque cursor from a previous response's next_cursor.
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
        description: Items per page.
      - name: refresh
        in: query
        required: false
        schema:
          type: string
          enum:
          - 'true'
        description: Accepted for compatibility; the feed reads through on every request.
      responses:
        '200':
          description: A page of the feed
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      required:
                      - feed_item_id
                      - type
                      - content_kind
                      - badge
                      - source
                      - authors
                      - engagement
                      properties:
                        feed_item_id:
                          type: string
                          description: Stable id, unique per item. Dedupe and analytics
                            key.
                          example: pm_8231
                        type:
                          type: string
                          enum:
                          - clip
                          - highlight
                          - editorial
                          description: Which renderer the client should use.
                        content_kind:
                          type: string
                          enum:
                          - epic
                          - blooper
                          - highlight
                          - editorial
                          description: Why the item was selected.
                        badge:
                          type: string
                          nullable: true
                          enum:
                          - featured
                          - throwback
                          - your_venue
                          description: 'Why this viewer is seeing the item, for the
                            label on the card. Always present; null when no reason
                            applies. At most one value, resolved in the order featured
                            > throwback > your_venue. featured — an editorial post
                            from HiFy. throwback — a game the viewer played in, old
                            enough to be worth remembering. your_venue — the viewer
                            did not play in it, but has played at that facility recently.
                            Viewer-dependent, so the same item can carry a different
                            badge for someone else. Clients should ignore values they
                            do not recognise, so new ones can be added without breaking
                            older builds.

                            '
                          example: throwback
                        source:
                          type: object
                          description: Type-specific payload (media, facility, game,
                            moments).
                        authors:
                          type: array
                          items:
                            type: object
                          description: Who is in the item.
                        engagement:
                          type: object
                          description: Reaction counts, and whether the viewer reacted.
                  next_cursor:
                    type: string
                    nullable: true
                    description: Pass back as cursor for the next page. Null on the
                      last page.
                  head_cursor:
                    type: string
                    nullable: true
                    description: Marks the top of the feed, for the new-items count.
                  empty_state:
                    type: string
                    nullable: true
                    enum:
                    - caught_up
                    - no_content
                    description: Why an empty page is empty. Null when the page has
                      items.
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - viewer is not a player
      produces:
      - application/json
  "/v1/explore/viewed":
    post:
      security:
      - UserToken: []
      tags:
      - Explore
      summary: Mark feed items as watched
      description: 'Records that the viewer finished watching one or more feed items
        so they drop out of their Explore feed. Batched so a fast-scrolling client
        need not issue one request per item. Idempotent — re-posting an already-viewed
        item is a no-op. Unparseable ids are skipped and returned under rejected_feed_item_ids
        rather than failing the whole batch.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - feed_item_ids
              properties:
                feed_item_ids:
                  type: array
                  items:
                    type: string
                  description: Feed item ids to mark watched (e.g. "pm_8231").
                  example:
                  - pm_8231
                  - pm_8242
      responses:
        '200':
          description: Views recorded
          content:
            application/json:
              schema:
                type: object
                properties:
                  accepted_feed_item_ids:
                    type: array
                    items:
                      type: string
                    description: Ids that were recorded (de-duplicated).
                    example:
                    - pm_8231
                    - pm_8242
                  rejected_feed_item_ids:
                    type: array
                    items:
                      type: string
                    description: Ids whose prefix did not name a known feed source.
                    example:
                    - xx_999
        '400':
          description: Bad request - feed_item_ids missing
        '401':
          description: Unauthenticated
        '403':
          description: Forbidden - viewer is not a player
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/facilities":
    get:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: List all facilities
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: city
        in: query
        schema:
          type: string
      - name: sport
        in: query
        schema:
          type: string
      - name: partner_id
        in: query
        schema:
          type: integer
      - name: country
        in: query
        description: ISO 3166-1 alpha-2 code. Players default to their own country;
          admins see every country unless one is given.
        schema:
          type: string
          example: AE
      responses:
        '200':
          description: List of facilities with their partners
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    facility:
                      type: object
                    partner:
                      type: object
    post:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Create a facility
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                facility:
                  type: object
                  properties:
                    name:
                      type: string
                    partner_id:
                      type: integer
                    operator_name:
                      type: string
                    operator_phonenumber:
                      type: string
                    address:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                    country_id:
                      type: integer
                    pincode:
                      type: string
                    geo:
                      type: object
                    icon_s3_path:
                      type: string
      responses:
        '201':
          description: Facility created successfully
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/facilities/search":
    get:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Search facilities, the user's country first
      description: One ranked stream of matches with the preferred country's facilities
        ahead of every other country, cut into pages. Each page is split into
        `facilities` (preferred country) and `other_facilities` (everyone else).
        The preferred country is `country` when given, otherwise the player's own;
        an admin without `country` has none, and then everything is under
        `facilities`. An unknown `country` is a 400.
      parameters:
      - name: q
        in: query
        required: true
        schema:
          type: string
      - name: lat
        in: query
        schema:
          type: number
      - name: lng
        in: query
        schema:
          type: number
      - name: country
        in: query
        description: ISO 3166-1 alpha-2 code of the preferred country.
        schema:
          type: string
          example: AE
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: A page of matches split by country; X-Total-Count and X-Total-Pages
            describe the whole stream
          content:
            application/json:
              schema:
                type: object
                properties:
                  facilities:
                    type: array
                    items:
                      type: object
                  other_facilities:
                    type: array
                    items:
                      type: object
                  pagination:
                    type: object
                    properties:
                      page:
                        type: integer
                      per_page:
                        type: integer
                      total_count:
                        type: integer
                        description: Matches across every country
                      country_count:
                        type: integer
                        description: Matches in the preferred country
  "/v1/facilities/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Get a facility
      responses:
        '200':
          description: Facility details
          content:
            application/json:
              schema:
                type: object
                properties:
                  partner:
                    type: object
    patch:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Update a facility
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                facility:
                  type: object
                  properties:
                    name:
                      type: string
                    partner_id:
                      type: integer
                    operator_name:
                      type: string
                    operator_phonenumber:
                      type: string
                    address:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                    country_id:
                      type: integer
                    pincode:
                      type: string
                    geo:
                      type: object
                    icon_s3_path:
                      type: string
      responses:
        '200':
          description: Facility updated
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Delete a facility
      responses:
        '204':
          description: Facility deleted successfully
  "/v1/facilities/{id}/arenas":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: in_facility_id
      in: query
      required: false
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Facilities
      summary: Get facility arenas
      responses:
        '200':
          description: Facility arenas
          content:
            application/json:
              schema:
                type: object
                properties:
                  facility:
                    type: object
                    properties:
                      game_creation_window_hours:
                        type: integer
                        description: "The facility's game creation window, in hours: 72 when any device on its arenas is on-demand, else 336 (14 days). Game create and update still validate the start time against the game's own arena, which can allow 336 for an arena with no devices."
                  arenas:
                    type: array
                    items:
                      type: object
  "/v1/edge/footage-requests":
    get:
      security:
      - DeviceToken: []
      tags:
      - Edge Footage Requests
      summary: Poll this device's open footage requests
      description: |
        Returns the calling device's COMPLETE set of open (non-terminal) footage requests, ordered by window_start. This is a reconcile endpoint, not a feed: the device treats the response as desired state and cancels locally anything missing from it, which is how a deleted game or a shrunk window propagates without a separate delete call.
        For that reason the response is deliberately never paginated and accepts no `since` filter -- either would silently cancel live requests on the device.
        Polling stamps last_polled_at on every returned request (a liveness signal) without touching updated_at.
      responses:
        '200':
          description: The device's complete open set (possibly empty).
          content:
            application/json:
              schema:
                type: object
                properties:
                  requests:
                    type: array
                    items:
                      "$ref": "#/components/schemas/FootageRequest"
        '401':
          description: Missing, malformed or unknown device bearer token.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeviceUnauthorized"
  "/v1/edge/footage-requests/{id}":
    patch:
      security:
      - DeviceToken: []
      tags:
      - Edge Footage Requests
      summary: Report progress or a terminal verdict for one request
      description: |
        The device reports what it found and what it uploaded. A terminal status (fulfilled, partial, no_footage, lost, failed) stamps completed_at and drops the request out of the open set returned by GET /v1/edge/footage-requests.
        The lookup is scoped to the calling device, so another device's request id returns 404 rather than 403 -- the response does not confirm that the row exists.
        segments_expected, segments_uploaded and segments_created are optional; omitting one -- or sending it as null or an empty string -- leaves the value an earlier report recorded.
        An open status (pending, acknowledged, uploading) sent for a request that is already terminal is ignored: nothing is written, and the response still returns 200 with the request's current terminal status. Only a change to the game reopens a closed request.
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: integer
        description: FootageRequest id, as returned by the poll endpoint.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - status
              properties:
                status:
                  type: string
                  enum:
                  - pending
                  - acknowledged
                  - uploading
                  - fulfilled
                  - partial
                  - no_footage
                  - lost
                  - failed
                  description: 'fulfilled/partial mean footage reached S3; no_footage
                    means the device looked and found nothing; lost means the device
                    recorded footage but deleted it under disk pressure before it
                    reached S3; failed means the attempt broke, which is NOT evidence
                    that footage never existed.

                    '
                segments_expected:
                  type: integer
                  nullable: true
                  description: Segments the device found inside the window.
                  example: 6
                segments_uploaded:
                  type: integer
                  description: Segments successfully uploaded so far.
                  example: 2
                segments_created:
                  type: integer
                  nullable: true
                  description: Segments the device has cut from the window before uploading. Falls when the classifier discards a segment.
                  example: 4
      responses:
        '200':
          description: Report accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    example: 42
                  status:
                    type: string
                    example: uploading
        '401':
          description: Missing, malformed or unknown device bearer token.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeviceUnauthorized"
        '404':
          description: 'No such request for the calling device -- either it does not
            exist or it belongs to another device.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: not_found
        '422':
          description: Unknown or missing status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: invalid_status
                  message:
                    type: string
                    example: Unknown status banana
  "/v1/game_participants":
    get:
      security:
      - UserToken: []
      tags:
      - Game Participants
      summary: List game participants
      parameters:
      - in: query
        name: arena_id
        schema:
          type: integer
      - in: query
        name: status
        schema:
          type: string
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
      responses:
        '200':
          description: List of game participants
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    game:
                      type: object
                      properties:
                        id:
                          type: integer
                        arena_id:
                          type: integer
                        start_time:
                          type: string
                          format: date-time
                        end_time:
                          type: string
                          format: date-time
                    user:
                      type: object
                      properties:
                        id:
                          type: integer
                        first_name:
                          type: string
                        last_name:
                          type: string
  "/v1/game_participants/{id}":
    patch:
      security:
      - UserToken: []
      tags:
      - Game Participants
      summary: Update game participant status
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                game_participant:
                  type: object
                  properties:
                    status:
                      type: string
                      enum:
                      - accepted
                      - rejected
      responses:
        '200':
          description: Updated game participant
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Invalid status
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
  "/v1/games":
    get:
      security:
      - UserToken: []
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: sport
        in: query
        schema:
          type: string
      - name: tournament_id
        in: query
        schema:
          type: string
      responses:
        '200':
          description: List of games
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - Games
      summary: Create a new game
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - game
              properties:
                game:
                  type: object
                  required:
                  - start_time
                  - duration_minutes
                  - arena_id
                  properties:
                    start_time:
                      type: string
                      format: date-time
                    duration_minutes:
                      type: integer
                    arena_id:
                      type: integer
                    user_photo_s3_path:
                      type: string
                    tournament_id:
                      type: string
      responses:
        '201':
          description: Game created
        '400':
          description: Bad request
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/join":
    post:
      security:
      - UserToken: []
      tags:
      - Games
      summary: Join a game by its invite code
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - game_code
              properties:
                game_code:
                  type: string
                user_photo_s3_path:
                  type: string
                si:
                  type: string
                  format: uuid
                  description: "Invite link's sharer. Used as referrer_id on the join event when it is another user's uuid; otherwise ignored."
      responses:
        '200':
          description: Successfully joined the game; returns the game
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: Game details
      consumes:
      - application/json
      produces:
      - application/json
    put:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                game:
                  type: object
                  properties:
                    start_time:
                      type: string
                      format: date-time
                    duration_minutes:
                      type: integer
                    arena_id:
                      type: integer
      responses:
        '200':
          description: Game updated
  "/v1/games/{game_id}/details":
    parameters:
    - name: game_id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Games
      summary: Get game details page
      description: 'Returns everything the game details page renders: the game card,
        the current user''s participation standing (selfie owed, whether they may
        leave), the content_state of the recording and, when content is available,
        the clips / photos / highlights / replays sections. When content is not
        yet available, sections is empty and empty_state carries the copy to show.

        content_delayed is only returned to clients whose X-App-Version /
        X-OTA-Datetime headers mark them as able to render it; older clients
        receive no_recording instead.

        A game still backing up from an on-demand arena is reported as
        content_available with partial true.

        The response is cached per game and user.

        '
      responses:
        '200':
          description: Game details page
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/GameDetails"
        '401':
          description: Unauthorized
        '404':
          description: Game not found
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/highlights":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: page
      in: query
      schema:
        type: integer
    - name: per_page
      in: query
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: Game highlights
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/clips":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: page
      in: query
      schema:
        type: integer
    - name: per_page
      in: query
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: Game clips
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/photos":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: page
      in: query
      schema:
        type: integer
    - name: per_page
      in: query
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: Game photos
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/users":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: page
      in: query
      schema:
        type: integer
    - name: per_page
      in: query
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: Game users
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/GameParticipant"
    post:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user
              properties:
                user:
                  type: object
                  required:
                  - id
                  properties:
                    id:
                      type: integer
      responses:
        '200':
          description: User added successfully
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/users/invite":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user
              properties:
                user:
                  type: object
                  required:
                  - phonenumber
                  properties:
                    phonenumber:
                      type: string
      responses:
        '200':
          description: User invited successfully
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/{id}/users/{user_id}/participation":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: user_id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      responses:
        '200':
          description: User participation details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/GameParticipant"
        '404':
          description: Not found
      consumes:
      - application/json
      produces:
      - application/json
    put:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - verification
              properties:
                verification:
                  type: string
      responses:
        '200':
          description: Participation updated
        '404':
          description: Not found
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/games/with-skus":
    get:
      security:
      - UserToken: []
      tags:
      - Games
      summary: Get games with SKUs
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: opportunity
        in: query
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Games with SKUs
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    game:
                      "$ref": "#/components/schemas/Game"
                    sku:
                      "$ref": "#/components/schemas/Sku"
        '400':
          description: Bad request
  "/v1/games/{id}/generate-annotation-tasks":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      responses:
        '200':
          description: Annotation tasks generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
  "/v1/highlight_moment_bundles":
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: List highlight moment bundles
      parameters:
      - name: status
        in: query
        schema:
          type: string
      - name: order_request_id
        in: query
        schema:
          type: integer
      - name: actor
        in: query
        schema:
          type: string
      - name: order
        in: query
        schema:
          type: string
          enum:
          - asc
          - desc
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of highlight moment bundles with pagination
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  pagination:
                    type: object
                    properties:
                      current_page:
                        type: integer
                      per_page:
                        type: integer
                      total_pages:
                        type: integer
                      total_count:
                        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Create a highlight moment bundle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                highlight_moment_bundle:
                  type: object
                  properties:
                    order_request_id:
                      type: integer
                play_moments:
                  type: array
                  items:
                    type: integer
                actor:
                  type: string
              required:
              - actor
      responses:
        '201':
          description: Highlight moment bundle created
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}":
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Get highlight moment bundle details
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Highlight moment bundle details
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Not found
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}/assign":
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Assign highlight moment bundle
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                actor:
                  type: string
                assigned_to:
                  type: string
              required:
              - actor
      responses:
        '200':
          description: Highlight moment bundle assigned
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}/approve":
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Approve highlight moment bundle
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                actor:
                  type: string
              required:
              - actor
      responses:
        '200':
          description: Highlight moment bundle approved
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}/request_changes":
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Request changes for highlight moment bundle
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                actor:
                  type: string
              required:
              - actor
      responses:
        '200':
          description: Changes requested for highlight moment bundle
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}/cancel":
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Cancel highlight moment bundle
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                actor:
                  type: string
              required:
              - actor
      responses:
        '200':
          description: Highlight moment bundle cancelled
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/highlight_moment_bundles/{id}/history":
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Moment Bundles
      summary: Get highlight moment bundle history
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Highlight moment bundle history
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
        '404':
          description: Not found
          content:
            application/json:
              schema:
                type: object
  "/v1/highlights":
    get:
      security:
      - UserToken: []
      tags:
      - Highlights
      summary: List all highlights
      responses:
        '200':
          description: List of highlights
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - Highlights
      summary: Create a highlight
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                highlight:
                  type: object
                  properties:
                    user_id:
                      type: string
                    game_id:
                      type: string
                    order_request_id:
                      type: string
                    playback_s3_path:
                      type: string
                    thumbnail_s3_path:
                      type: string
                use_highlight_bundles:
                  type: boolean
                  default: false
                actor:
                  type: string
      responses:
        '201':
          description: Highlight created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Highlight"
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/highlights/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    get:
      security:
      - UserToken: []
      tags:
      - Highlights
      summary: Get a specific highlight
      responses:
        '200':
          description: Highlight details
      consumes:
      - application/json
      produces:
      - application/json
    put:
      security:
      - UserToken: []
      tags:
      - Highlights
      summary: Update a highlight
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                highlight:
                  type: object
                  properties:
                    user_id:
                      type: string
                    game_id:
                      type: string
                    order_request_id:
                      type: string
                    playback_s3_path:
                      type: string
                    thumbnail_s3_path:
                      type: string
      responses:
        '200':
          description: Highlight updated
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
    delete:
      security:
      - UserToken: []
      tags:
      - Highlights
      summary: Delete a highlight
      responses:
        '204':
          description: Highlight deleted
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/highlights/requests":
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Requests
      summary: List highlight requests
      operationId: listHighlightRequests
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: status
        in: query
        schema:
          type: string
          enum:
          - open
          - in_progress
          - in_review
          - done
          - archived
      - name: facility_id
        in: query
        schema:
          type: string
      - name: arena_id
        in: query
        schema:
          type: string
      - name: start_date
        in: query
        schema:
          type: string
      - name: highlight_bundle_status
        in: query
        schema:
          type: string
          enum:
          - pending
          - zip_processing
          - zip_ready
          - in_progress
          - pending_approval
          - approved
          - cancelled
      responses:
        '200':
          description: List of highlight requests
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/HighlightRequest"
    post:
      security:
      - UserToken: []
      tags:
      - Highlight Requests
      summary: Create a highlight request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                highlight_request:
                  type: object
                  properties:
                    user_id:
                      type: string
                    game_id:
                      type: string
                    status:
                      type: string
      responses:
        '200':
          description: Highlight request created
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/highlights/requests/{id}/moments":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    - name: page
      in: query
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Requests
      summary: Get request moments
      operationId: getRequestMoments
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: arena_id
        in: query
        schema:
          type: integer
      - name: is_epic
        in: query
        schema:
          type: boolean
      - name: occurred_at_from
        in: query
        schema:
          type: string
          format: date-time
      - name: occurred_at_to
        in: query
        schema:
          type: string
          format: date-time
      - name: moment
        in: query
        schema:
          type: string
      - name: player
        in: query
        schema:
          type: string
      - name: tags
        in: query
        schema:
          type: string
      responses:
        '200':
          description: List of play moments
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/highlights/requests/{id}/highlights":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    get:
      security:
      - UserToken: []
      tags:
      - Highlight Requests
      summary: Get highlights for a request
      responses:
        '200':
          description: List of highlights
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Highlight"
  "/v1/highlights/requests/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    put:
      security:
      - UserToken: []
      tags:
      - Highlight Requests
      summary: Update a highlight request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                highlight_request:
                  type: object
                  properties:
                    user_id:
                      type: string
                    game_id:
                      type: string
                    status:
                      type: string
      responses:
        '200':
          description: Highlight request updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/HighlightRequest"
        '422':
          description: Unprocessable entity
  "/v1/import_runs/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - AccessKeyId: []
        Signature: []
      - AccessKeyId: []
        AccessSecret: []
      tags:
      - Import Runs
      summary: Poll one import run (partner)
      description: 'The partner''s read of its own run, scoped to the calling integration.
        Authenticated by the integration''s credentials, not a user session: X-Access-Key-Id
        plus X-Signature for signing kinds, or X-Access-Key-Id plus X-Access-Secret
        for static_token kinds. The errors key here is the partner-facing alias for
        the admin payload''s error_details.

        '
      responses:
        '200':
          description: Import run status
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - open
                    - in_progress
                    - completed
                    - failed
                    - staged
                  kind:
                    type: string
                  started_at:
                    type: string
                    format: date-time
                    nullable: true
                  finished_at:
                    type: string
                    format: date-time
                    nullable: true
                  errors:
                    type: array
                    items:
                      type: object
                  summary:
                    type: object
        '404':
          description: No such run for this integration
  "/v1/admin/jobs/trigger":
    post:
      security:
      - UserToken: []
      tags:
      - Admin Jobs
      summary: Trigger a background job on demand
      description: 'Admin-only endpoint that enqueues any ApplicationJob by class
        name. Accepts an optional `params` hash that is forwarded to the job (as keyword
        arguments by default, or transformed by a registered custom transformer).

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - job
              properties:
                job:
                  type: string
                  description: The job class name to enqueue
                  example: MomentDetectionInitializerJob
                params:
                  type: object
                  description: 'Optional parameters to pass to the job. Shape depends
                    on the job being triggered.

                    '
                  additionalProperties: true
                  example:
                    record_type: OrderRequest
                    record_id: 123
      responses:
        '202':
          description: Job successfully enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: queued
                  job:
                    type: string
                    example: MomentDetectionInitializerJob
        '401':
          description: Unauthorized – missing or invalid auth token
        '403':
          description: Forbidden – caller is not an admin
        '422':
          description: 'Unprocessable entity – job name is blank, unknown, or does
            not refer to an ApplicationJob subclass

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: 'Unknown job: NonExistentJob'
  "/v1/live_streams":
    get:
      security:
      - UserToken: []
      tags:
      - LiveStreams
      summary: List all live streams
      parameters:
      - name: arena_id
        in: query
        schema:
          type: integer
      - name: game_id
        in: query
        schema:
          type: integer
      - name: tournament_id
        in: query
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of live streams
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/LiveStream"
    post:
      security:
      - UserToken: []
      tags:
      - LiveStreams
      summary: Create a new live stream
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                live_stream:
                  type: object
                  properties:
                    game_id:
                      type: integer
                    camera_id:
                      type: integer
                    destination:
                      type: object
                      description: 'Which account the stream broadcasts to. Mandatory.
                        A hify destination provisions its own YouTube stream key and
                        must not carry key material; a partner destination must supply
                        the key, because the account is not ours to provision from.

                        '
                      properties:
                        account:
                          type: string
                          enum:
                          - hify
                          - partner
                        key_value:
                          type: string
                          description: 'Required for account=partner, rejected for
                            account=hify.

                            '
                        stream_host:
                          type: string
                          description: 'RTMP ingest host including the scheme. Partner
                            only; defaults to rtmp://a.rtmp.youtube.com/live2.

                            '
                        visibility:
                          type: string
                          enum:
                          - public
                          - unlisted
                          - private
                          default: public
                          description: 'Visibility of the YouTube broadcast. Hify
                            only, ignored for account=partner. Unlisted suits test
                            streams.

                            '
                      required:
                      - account
                  required:
                  - game_id
                  - camera_id
                  - destination
              required:
              - live_stream
      responses:
        '201':
          description: Live stream created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/LiveStream"
        '422':
          description: 'Invalid destination — absent, unknown account, a partner destination
            with no key, or a hify destination carrying key material. Nothing is written.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        message:
                          type: string
                        field:
                          type: string
                  id:
                    type: string
        '502':
          description: 'YouTube stream key provisioning failed. No live stream, destination
            or key was created; retry the request.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  kind:
                    type: string
                  id:
                    type: string
  "/v1/live_streams/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - LiveStreams
      summary: Get a specific live stream
      responses:
        '200':
          description: Live stream details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/LiveStream"
    delete:
      security:
      - UserToken: []
      tags:
      - LiveStreams
      summary: Delete a live stream
      responses:
        '204':
          description: Live stream deleted
  "/v1/live_streams/{live_stream_id}/destinations":
    parameters:
    - name: live_stream_id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - LiveStreamDestinations
      summary: List destinations for a live stream
      responses:
        '200':
          description: List of destinations
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/LiveStreamDestination"
  "/v1/live_streams/{live_stream_id}/destinations/{id}":
    parameters:
    - name: live_stream_id
      in: path
      required: true
      schema:
        type: integer
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - LiveStreamDestinations
      summary: Get a specific destination
      responses:
        '200':
          description: Destination details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/LiveStreamDestination"
    delete:
      security:
      - UserToken: []
      tags:
      - LiveStreamDestinations
      summary: Delete a destination
      responses:
        '204':
          description: Destination deleted
  "/v1/auth":
    post:
      tags:
      - Auth
      summary: Login with phone number
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - login
              properties:
                login:
                  type: object
                  required:
                  - phonenumber
                  properties:
                    phonenumber:
                      type: string
      responses:
        '200':
          description: Successful login
          content:
            application/json:
              schema:
                type: object
        '429':
          description: Too many requests
  "/v1/auth/retry":
    post:
      tags:
      - Auth
      summary: Retry login
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - login
              properties:
                login:
                  type: object
                  required:
                  - phonenumber
                  properties:
                    phonenumber:
                      type: string
      responses:
        '200':
          description: Successful retry
          content:
            application/json:
              schema:
                type: object
        '429':
          description: Too many requests
  "/v1/auth/verify":
    post:
      tags:
      - Auth
      summary: Verify OTP
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - login
              properties:
                login:
                  type: object
                  required:
                  - phonenumber
                  - otp
                  properties:
                    phonenumber:
                      type: string
                    otp:
                      type: string
      responses:
        '200':
          description: Successful verification
          content:
            application/json:
              schema:
                type: object
          headers:
            X-User-Onboarding-Required:
              schema:
                type: boolean
            X-Deletion-Cancelled:
              description: 'True when this sign-in called off a pending account deletion.

                '
              schema:
                type: boolean
        '401':
          description: Unauthorized
        '429':
          description: Too many requests
  "/v1/auth/logout":
    post:
      tags:
      - Auth
      summary: Logout
      security:
      - UserToken: []
      responses:
        '200':
          description: Successful logout
          content:
            application/json:
              schema:
                type: object
  "/v1/matches/{match_id}/claim":
    parameters:
    - name: match_id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Match Claims
      summary: Claim a match for scoring
      description: 'Creates an active claim for the current user on the match. A referee
        must claim a match before submitting score entries. The match must not be
        completed.

        '
      responses:
        '201':
          description: Claim created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MatchClaim"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — only admins and referees may claim a match
        '422':
          description: Unprocessable entity (e.g. match already completed)
  "/v1/matches/{match_id}/claim/force":
    parameters:
    - name: match_id
      in: path
      required: true
      schema:
        type: integer
    delete:
      security:
      - UserToken: []
      tags:
      - Match Claims
      summary: Force-release the active claim (admin takeover)
      description: |
        Admin-only takeover door for the destroyed-device case: releases the match's active claim regardless of who holds it, bumping the writer epoch. The superseded device's next event flush gets 409 stale_claim; the replacement device bootstraps from GET scoring_events and continues at acked_seq + 1.

        Unlike PATCH /v1/matches/{id}/release this deliberately leaves the match in_progress, since a takeover means scoring continues on another device.
      responses:
        '204':
          description: Claim force-released, or no active claim to release
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admins only
        '404':
          description: Match not found
  "/v1/matches/{match_id}/sets/{id}":
    parameters:
    - name: match_id
      in: path
      required: true
      schema:
        type: integer
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Match Sets
      summary: Update a set with scores
      description: 'Updates the set by providing score_a and score_b. The winner_side
        is automatically computed from the scores.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - match_set
              properties:
                match_set:
                  type: object
                  required:
                  - score_a
                  - score_b
                  properties:
                    score_a:
                      type: integer
                    score_b:
                      type: integer
      responses:
        '200':
          description: Set updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MatchSet"
        '401':
          description: Unauthorized
        '404':
          description: Match or set not found
        '422':
          description: Invalid scores
  "/v1/tournaments/{tournament_id}/games/{game_id}/matches":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: game_id
      in: path
      required: true
      schema:
        type: integer
    post:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Create a match within a tournament game
      description: 'Creates a match under the given game. Optionally provide side_a
        and side_b participants (User or AnonymousTournamentParticipant) in the request
        body. An AnonymousTournamentParticipant must belong to the tournament the
        game is part of.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - match
              properties:
                match:
                  type: object
                  required:
                  - stage
                  properties:
                    sequence_number:
                      type: integer
                    round_number:
                      type: integer
                    stage:
                      type: string
                      enum:
                      - league
                      - quarterfinal
                      - semifinal
                      - knockout
                      - final
                      - third_place
                    category:
                      type: string
                      description: Display category, e.g. "Open Mixed Doubles"
                    side_a:
                      type: object
                      properties:
                        participant_type:
                          type: string
                          enum:
                          - User
                          - AnonymousTournamentParticipant
                        participant_id:
                          type: string
                          format: uuid
                    side_b:
                      type: object
                      properties:
                        participant_type:
                          type: string
                          enum:
                          - User
                          - AnonymousTournamentParticipant
                        participant_id:
                          type: string
                          format: uuid
      responses:
        '201':
          description: Match created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Match"
        '401':
          description: Unauthorized
        '422':
          description: Unprocessable entity
  "/v1/matches/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Show a match
      description: 'Returns match details including match_participants, match_sets,
        and the enabled cameras of the arena where the match is played.

        '
      responses:
        '200':
          description: Match details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Match"
        '401':
          description: Unauthorized
        '404':
          description: Match not found
  "/v1/matches/{id}/start":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Start a match
      description: 'Transitions the match from pending to in_progress. Refused if
        another match on the same game is already live, since both would emit onto
        that game''s score channel.

        '
      responses:
        '200':
          description: Match started
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Match"
        '401':
          description: Unauthorized
        '404':
          description: Match not found
        '422':
          description: 'Invalid state transition, or another match is already live
            on this match''s game

            '
  "/v1/matches/{id}/start_scoring":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Start scoring a match
      description: 'Claims the match for the current user, transitions it to in_progress,
        seeds the initial round tree (set + game), and optionally reassigns the match
        to the tournament''s game on a different court. This is the combined "claim
        + start + seed rounds" action used by referees.

        '
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                match:
                  type: object
                  properties:
                    court_number:
                      type: integer
                      description: 'Optional — the court the match is actually being
                        played on. When it differs from the match''s current court,
                        the match is reassigned to the tournament''s game already
                        scheduled on that court for this match''s time slot; the current
                        game''s court is never changed. Returns 404 if the court does
                        not exist at the facility, and 422 if the tournament has no
                        game on it in that slot, or that game already has another
                        live match.

                        '
                    first_serve_side:
                      type: string
                      enum:
                      - side_a
                      - side_b
                      - a
                      - b
                      description: 'Optional — which side served first. Persisted
                        on first start_scoring only; not overwritten on subsequent
                        calls.

                        '
                    device_id:
                      type: string
                      description: 'Optional — the scoring device registering as the
                        match''s single writer. A claim from a different device bumps
                        the writer epoch: the previous claim is released so the old
                        device''s unsynced events are fenced at ingest (409 stale_claim)
                        instead of interleaving into the new log.

                        '
      responses:
        '200':
          description: Match started for scoring
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Match"
        '401':
          description: Unauthorized
        '404':
          description: 'Match not found, or court_number does not resolve to a court
            at the facility

            '
        '422':
          description: 'Invalid state transition, match already claimed, invalid first_serve_side,
            a court change on a match that is not part of a tournament, the tournament
            has no game on the requested court in this match''s time slot, or another
            match is already live on the target court''s game (starting would corrupt
            that court''s livestream)

            '
  "/v1/matches/{id}/release":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    - name: device_id
      in: query
      required: false
      schema:
        type: string
      description: 'Alternative to the request body for clients that cannot send one.
        The body value wins when both are given.

        '
    patch:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Release a match
      description: |
        Releases the current user's active claim on the match (status becomes released and released_at is stamped) AND hands the match back to the pool: an in_progress match transitions back to pending so another scorer can claim it, which also frees the court for the next match on the game and drops this match from the game's live score channel. A completed match keeps its status.

        The scoring event log, match_rounds and score_entries all survive, so the next claimer resumes the match via start_scoring rather than restarting it.

        Idempotent: when the user holds no active claim the response is still 204 and neither the match nor anyone else's claim is touched, so a client retry after a timed-out-but-successful release is safe.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                match:
                  type: object
                  properties:
                    device_id:
                      type: string
                      description: 'Optional — the device that registered as the match''s
                        writer. Fences the release the same way event ingest is fenced:
                        if a re-claim from a newer device already bumped the epoch,
                        this returns 409 stale_claim rather than kicking the scorer
                        who replaced it. Omitting it skips the fence.

                        '
      responses:
        '204':
          description: 'Claim released (or no active claim to release — see idempotency)

            '
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — only admins and referees may release a match
        '404':
          description: Match not found
        '409':
          description: 'stale_claim — the requesting device is not the registered
            writer for the active claim; nothing was released

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    enum:
                    - stale_claim
  "/v1/matches/{id}/complete":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Complete a match from its stored event log (sync fast-path)
      description: |
        Applies the completion already present in the match's scoring event log (a durably flushed match_completed marker) synchronously, so a client that submits results and immediately refetches sees the match completed instead of racing the async projection job. The completion is read from the log, never from the caller — a request cannot invent one.

        Score rows, broadcast and partner webhooks stay with the async projection job; the sequence watermarks make both paths converge idempotently in either order.

        Idempotent: an already-completed match responds 204 without change, so a retry after a timed-out-but-successful call is safe.
      responses:
        '204':
          description: The match is completed (applied now, or already was)
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — only admins and referees may complete a match
        '404':
          description: Match not found
        '422':
          description: 'no_completion_event — the log holds no effective match_completed
            (the match never ended, or the completion was undone by a later match_reopened);
            or invalid_state — the match is not in a state that can complete (e.g.
            released back to pending before the completion arrived)

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    enum:
                    - no_completion_event
                    - invalid_state
  "/v1/moment_categories":
    get:
      summary: List moment categories
      security:
      - UserToken: []
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of moment categories
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
    post:
      summary: Create a moment category
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                moment_category:
                  type: object
                  properties:
                    name:
                      type: string
      responses:
        '201':
          description: Moment category created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  name:
                    type: string
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/moment_categories/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      summary: Update a moment category
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                moment_category:
                  type: object
                  properties:
                    name:
                      type: string
      responses:
        '200':
          description: Moment category updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  name:
                    type: string
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      summary: Delete a moment category
      security:
      - UserToken: []
      responses:
        '204':
          description: Moment category deleted
  "/v1/moments":
    get:
      security:
      - UserToken: []
      tags:
      - Moments
      summary: List all moments
      operationId: getMoments
      parameters:
      - name: sport_id
        in: query
        schema:
          type: integer
      - name: moment_category_id
        in: query
        schema:
          type: integer
      - name: moment
        in: query
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of moments
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    moment_category:
                      type: object
                    sport:
                      type: object
    post:
      security:
      - UserToken: []
      tags:
      - Moments
      summary: Create a moment
      operationId: createMoment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                moment:
                  type: object
                  properties:
                    sport_id:
                      type: integer
                    moment_category_id:
                      type: integer
                    moment:
                      type: string
                    is_clippable:
                      type: boolean
      responses:
        '201':
          description: Moment created
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/moments/with_multiple_sports":
    post:
      security:
      - UserToken: []
      tags:
      - Moments
      summary: Create moments with multiple sports
      operationId: createMomentsWithMultipleSports
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                moments:
                  type: object
                  properties:
                    moment_category_id:
                      type: integer
                    moment:
                      type: string
                    is_clippable:
                      type: boolean
                    sport_ids:
                      type: array
                      items:
                        type: integer
      responses:
        '201':
          description: Moments created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
  "/v1/moments/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Moments
      summary: Update a moment
      operationId: updateMoment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                moment:
                  type: object
                  properties:
                    sport_id:
                      type: integer
                    moment_category_id:
                      type: integer
                    moment:
                      type: string
                    is_clippable:
                      type: boolean
      responses:
        '200':
          description: Moment updated
          content:
            application/json:
              schema:
                type: object
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Moments
      summary: Delete a moment
      operationId: deleteMoment
      responses:
        '204':
          description: Moment deleted
  "/v1/order_requests":
    get:
      security:
      - UserToken: []
      tags:
      - Order Requests
      summary: List Order requests
      operationId: listOrderRequests
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: status
        in: query
        schema:
          type: string
          enum:
          - open
          - in_progress
          - in_review
          - done
          - archived
      - name: facility_id
        in: query
        schema:
          type: string
      - name: arena_id
        in: query
        schema:
          type: string
      - name: start_date
        in: query
        schema:
          type: string
      - name: id
        in: query
        schema:
          type: string
      - name: type
        in: query
        schema:
          type: string
      - name: attention_state
        in: query
        description: Filter by stage group. Accepts a single value or an array; unknown
          values return no rows.
        schema:
          oneOf:
          - type: string
          - type: array
            items:
              type: string
        example: ready_to_bundle
      - name: assignee
        in: query
        description: Filter by the current human owner's email, or the literal `unassigned`.
        schema:
          type: string
        example: priya@hify.club
      - name: highlight_bundle_status
        in: query
        description: Filter by the status of an associated highlight moment bundle.
        schema:
          type: string
      - name: highlight_bundle_assigned_to
        in: query
        description: Filter by the editor assigned on a non-terminal highlight moment
          bundle.
        schema:
          type: string
      responses:
        '200':
          description: List of order requests
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: object
                required:
                - order_requests
                - distinct_types
                - attention_summary
                - attention_filter_options
                - stuck_states
                properties:
                  order_requests:
                    type: array
                    items:
                      "$ref": "#/components/schemas/OrderRequest"
                  distinct_types:
                    type: array
                    items:
                      type: string
                    example:
                    - HighlightRequest
                    - ReplayRequest
                    - ShortRequest
                  attention_summary:
                    type: object
                    additionalProperties:
                      type: integer
                    description: 'Row counts per attention_state group for the ops-board
                      tiles. Computed on the filtered scope before pagination, ignoring
                      the attention_state filter itself; rows with no stage are excluded.

                      '
                    example:
                      annotating: 7
                      ready_to_bundle: 3
                      needs_editor: 2
                      in_review: 4
                      stuck: 1
                  attention_filter_options:
                    type: array
                    items:
                      type: string
                    description: Every attention_state group, for building the filter's
                      option list.
                  stuck_states:
                    type: array
                    items:
                      type: string
                    description: The subset of groups meaning "stopped, needs a human".
                    example:
                    - stuck
  "/v1/order_requests/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    put:
      security:
      - UserToken: []
      tags:
      - Order Requests
      summary: Update a Order request
      description: 'Assignment and attribute changes are applied in one transaction:
        a rejected update also rolls back the assignment. processing_state, processing_details
        and next_attempt_at are job-owned and not writable here.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                order_request:
                  type: object
                  properties:
                    user_id:
                      type: string
                    game_id:
                      type: string
                    status:
                      type: string
                      enum:
                      - open
                      - in_progress
                      - in_review
                      - done
                      - archived
                    assignee:
                      type: string
                      nullable: true
                      description: 'Email of the annotator/editor taking ownership.
                        Empty string or null unassigns. Only ShortRequest and HighlightRequest
                        accept an assignee; a ReplayRequest returns 400.

                        '
                      example: priya@hify.club
      responses:
        '200':
          description: Order request updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderRequest"
        '400':
          description: Bad request — e.g. assigning a request type with no annotation
            step (ReplayRequest)
        '422':
          description: Unprocessable entity — e.g. invalid status or malformed assignee
            email
  "/v1/orders":
    post:
      security:
      - UserToken: []
      tags:
      - Orders
      summary: Create a new order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                order:
                  type: object
                  properties:
                    sku_id:
                      type: string
                    game_id:
                      type: string
                    tournament_id:
                      type: string
                      format: uuid
                    note:
                      type: string
                  required:
                  - sku_id
                  anyOf:
                  - required:
                    - game_id
                  - required:
                    - tournament_id
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Bad request - mandatory params missing
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        '404':
          description: Not found - mandatory params incorrect
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
  "/v1/orders/{order_number}/verify-payment":
    post:
      security:
      - UserToken: []
      tags:
      - Orders
      summary: Verify payment for an order
      parameters:
      - name: order_number
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                order:
                  type: object
                  properties:
                    pg_payment_id:
                      type: string
                    pg_payment_signature:
                      type: string
                  required:
                  - pg_payment_id
                  - pg_payment_signature
      responses:
        '200':
          description: Payment verification status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
        '400':
          description: Bad request - mandatory params missing
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        '404':
          description: Order not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
  "/v1/partner_members":
    get:
      summary: List all partner members
      security:
      - UserToken: []
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: A list of partner members
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/PartnerMember"
    post:
      summary: Create a partner member
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner_member:
                  type: object
                  properties:
                    name:
                      type: string
                    phonenumber:
                      type: string
                    email:
                      type: string
                    department:
                      type: string
      responses:
        '201':
          description: Partner member created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PartnerMember"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/partner_members/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      summary: Update a partner member
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner_member:
                  type: object
                  properties:
                    name:
                      type: string
                    phonenumber:
                      type: string
                    email:
                      type: string
                    department:
                      type: string
      responses:
        '200':
          description: Partner member updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PartnerMember"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      summary: Delete a partner member
      security:
      - UserToken: []
      responses:
        '204':
          description: Partner member deleted
  "/v1/partners":
    get:
      security:
      - UserToken: []
      tags:
      - Partners
      summary: List all partners
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: name
        in: query
        schema:
          type: string
      - name: city
        in: query
        schema:
          type: string
      - name: state
        in: query
        schema:
          type: string
      - name: country
        in: query
        schema:
          type: string
      - name: pincode
        in: query
        schema:
          type: string
      responses:
        '200':
          description: List of partners
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Partner"
    post:
      security:
      - UserToken: []
      tags:
      - Partners
      summary: Create a new partner
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner:
                  type: object
                  properties:
                    name:
                      type: string
                    address:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                    country:
                      type: string
                    pincode:
                      type: string
      responses:
        '201':
          description: Partner created successfully
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Partner"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/partners/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      security:
      - UserToken: []
      tags:
      - Partners
      summary: Update a partner
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                partner:
                  type: object
                  properties:
                    name:
                      type: string
                    address:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                    country:
                      type: string
                    pincode:
                      type: string
      responses:
        '200':
          description: Partner updated successfully
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Partner"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Partners
      summary: Delete a partner
      responses:
        '204':
          description: Partner deleted successfully
  "/v1/payment-history":
    get:
      security:
      - UserToken: []
      summary: Unified payment history (one-time orders + subscription charges)
      parameters:
      - in: query
        name: kind
        schema:
          type: string
          enum:
          - order
          - subscription_charge
        required: false
        description: Filter to one source (the app's orders-vs-subscriptions toggle)
      - in: query
        name: start_time
        schema:
          type: string
          format: date-time
        required: false
        description: 'Inclusive start of a date range, ISO-8601 with an offset (build
          it in available_filters.time_zone). Applies only together with end_time;
          either one alone, or an unparseable value, is ignored, as in the gallery
          APIs.

          '
      - in: query
        name: end_time
        schema:
          type: string
          format: date-time
        required: false
        description: Exclusive end of the date range. See start_time.
      - in: query
        name: facilities[]
        schema:
          type: array
          items:
            type: integer
        required: false
        description: 'Only orders whose game was at one of these venues. Venue is
          an order concept, so this filter excludes subscription charges.

          '
      - in: query
        name: arenas[]
        schema:
          type: array
          items:
            type: integer
        required: false
        description: Only orders played on one of these courts. Excludes subscription
          charges.
      - in: query
        name: sort_by
        schema:
          type: string
          enum:
          - Newest
          - Oldest
        required: false
        description: Defaults to Newest; any other value falls back to Newest.
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
      responses:
        '200':
          description: 'Gallery-style envelope. data holds the entries interleaved
            by date (order per sort, newest by default); pagination is also sent as
            X-Total-Count / X-Total-Pages headers.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        kind:
                          type: string
                          enum:
                          - order
                          - subscription_charge
                          description: Routes the client to the right detail screen
                        source_id:
                          type: string
                        number:
                          type: string
                          example: HIFY-SUB-20260813-1A2B3C4D
                        amount:
                          type: string
                        currency_code:
                          type: string
                          example: INR
                        status:
                          type: string
                          enum:
                          - processing
                          - done
                          - failed
                          - refunded
                          description: The payment outcome
                        order_status:
                          type: string
                          nullable: true
                          enum:
                          - open
                          - paid
                          - on_hold
                          - in_progress
                          - fulfilled
                          - refunded
                          -
                          description: 'The order''s fulfilment stage, which status
                            folds into done. Null for subscription charges.

                            '
                        failure_reason:
                          type: string
                          nullable: true
                          description: Gateway-reported cause on failed subscription
                            charges
                        plan_name:
                          type: string
                          nullable: true
                          description: 'The plan this charge was for, as of the charge
                            (stays correct after upgrades). Null for orders.

                            '
                        payment_method:
                          type: object
                          nullable: true
                          description: 'How this payment was made, e.g. { method:
                            "upi", payment_method_details: { vpa: "…" } }. Null for
                            an order that never reached the gateway (a fully redeemed
                            benefit).

                            '
                        base_amount:
                          type: string
                          nullable: true
                          description: GST-exclusive amount (MRP). Null for orders.
                        tax_amount:
                          type: string
                          nullable: true
                          description: GST (18%, included in amount). Null for orders.
                        has_invoice:
                          type: boolean
                          description: 'Show the Invoice icon. True once a paid subscription
                            charge''s invoice link is saved (usually within minutes
                            of the charge); always false for orders.

                            '
                        subscription_id:
                          type: integer
                          nullable: true
                          description: 'With source_id, the path to the invoice: GET
                            /v1/subscriptions/{subscription_id}/charges/{source_id}/invoice
                            returns { invoice_url }. Null for orders.

                            '
                        occurred_at:
                          type: string
                          format: date-time
                  pagination:
                    type: object
                    properties:
                      current_page:
                        type: integer
                      per_page:
                        type: integer
                      total_pages:
                        type: integer
                      total_count:
                        type: integer
                  available_filters:
                    type: object
                    description: 'Options for the filter chips, computed from all
                      of the user''s history and never from the applied filters.

                      '
                    properties:
                      days:
                        type: array
                        description: 'Every date with at least one entry, oldest first,
                          in time_zone. The app groups these into months for the Month
                          chip.

                          '
                        items:
                          type: string
                          example: '2026-10-03'
                      facilities:
                        type: array
                        description: The venues of the user's orders, with their courts.
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            name:
                              type: string
                            arenas:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: integer
                                  name:
                                    type: string
                      sort_by:
                        type: array
                        description: Empty when the user has no history.
                        items:
                          type: string
                          enum:
                          - Newest
                          - Oldest
                      time_zone:
                        type: string
                        example: Asia/Kolkata
                        description: 'The zone the days are in: the user''s country
                          zone, or the default country''s. Build start_time/end_time
                          in it. Always present.

                          '
  "/v1/webhooks/payments/razorpay-delivery-report":
    post:
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Webhook processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  message:
                    type: string
                    example: Webhook processed successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: error
                  message:
                    type: string
                    example: Invalid request
      parameters:
      - in: header
        name: X-Razorpay-Signature
        schema:
          type: string
        required: true
        description: Razorpay signature for webhook verification
  "/v1/webhooks/otp/{provider}":
    post:
      tags:
      - Webhooks
      summary: OTP delivery callback from a messaging provider
      description: Unauthenticated; the provider signs nothing, so the
        X-Callback-Token header must match the provider's callback token. A failed delivery
        marks the login attempt otp_delivery_failed with the provider's reason;
        other events are logged.
      parameters:
      - name: provider
        in: path
        required: true
        schema:
          type: string
          example: gupshup_whatsapp
      - name: X-Callback-Token
        in: header
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: The provider's event payload as it posts it
      responses:
        '200':
          description: Event accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        '400':
          description: Body is not JSON
        '404':
          description: Unknown provider or wrong token
  "/v1/plans":
    get:
      security:
      - UserToken: []
      summary: List active paid plans with prices and entitlements
      responses:
        '200':
          description: Active plans ordered by position
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                      example: pro
                    display_name:
                      type: string
                      example: Pro
                    tier:
                      type: string
                      enum:
                      - free
                      - paid
                    position:
                      type: integer
                    entitlements:
                      type: array
                      items:
                        type: object
                        properties:
                          feature:
                            type: string
                            example: var_review
                          limit_type:
                            type: string
                            enum:
                            - unlimited
                            - quota
                          quota:
                            type: integer
                            nullable: true
                          reset_period:
                            type: string
                            enum:
                            - lifetime
                            - billing_cycle
                            - calendar_month
                    prices:
                      type: array
                      items:
                        type: object
                        properties:
                          id:
                            type: integer
                          billing_period:
                            type: string
                            enum:
                            - monthly
                            - yearly
                          amount:
                            type: string
                            example: '100.0'
                          currency_code:
                            type: string
                            example: INR
        '403':
          description: Not authorized
  "/v1/play_moments":
    get:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: List play moments
      parameters: &play_moments_filters
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      - name: annotation_task_id
        in: query
        schema:
          type: integer
      - name: arena_id
        in: query
        schema:
          type: integer
      - name: start_time
        in: query
        schema:
          type: string
          format: date-time
      - name: end_time
        in: query
        schema:
          type: string
          format: date-time
      - name: moment
        in: query
        schema:
          type: string
      - name: tag
        in: query
        schema:
          type: string
      - name: player_first_name
        in: query
        schema:
          type: string
      - name: player_last_name
        in: query
        schema:
          type: string
      - name: mode
        in: query
        schema:
          type: string
      - name: verification
        in: query
        schema:
          type: string
      - name: is_epic
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: Successful response
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: Create a play moment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - play_moments
              properties:
                play_moments:
                  type: object
                  required:
                  - annotation_task_id
                  - tick
                  properties:
                    annotation_task_id:
                      type: integer
                    tick:
                      type: number
                    is_epic:
                      type: boolean
                    tags:
                      type: array
                      items:
                        type: string
                    moment_ids:
                      type: array
                      items:
                        type: integer
                    user_ids:
                      type: array
                      items:
                        type: integer
                    players:
                      type: array
                      items:
                        "$ref": "#/components/schemas/PlayerType"
                      description: Array of player objects with type and id
                    start_tick:
                      type: integer
                    end_tick:
                      type: integer
                    reviewed_by:
                      type: string
                      description: Optional. Recorded in the audit trail via PaperTrail.
                      example: user123
      responses:
        '201':
          description: Play moment created
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/play_moments/spans":
    get:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: List play moments grouped into spans
      description: >
        Groups the same play moment groups returned by GET /v1/play_moments into
        spans. A span is every play moment group of one annotation task that has
        the exact same start_tick and end_tick. Each entry in ticks has the same
        shape as an item from GET /v1/play_moments, so it can be passed unchanged
        to POST /v1/play_moments, bulk_review and bulk_delete. Accepts the same
        filters as GET /v1/play_moments. Spans are ordered by their earliest
        occurred_at, and page, per_page, X-Total-Count and X-Total-Pages count
        spans, not ticks.
      parameters: *play_moments_filters
      responses:
        '200':
          description: Successful response
          headers:
            X-Total-Count:
              description: Total number of spans
              schema:
                type: integer
            X-Total-Pages:
              description: Total number of pages of spans
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    annotation_task_id:
                      type: integer
                    start_tick:
                      type: integer
                      description: Shared start_tick of every play moment group in the span
                    end_tick:
                      type: integer
                      description: Shared end_tick of every play moment group in the span
                    occurred_at:
                      type: string
                      format: date-time
                      description: Earliest occurred_at across the span's play moment groups
                    ticks:
                      type: array
                      description: >
                        The span's play moment groups, ordered by tick. Same shape
                        as an item from GET /v1/play_moments.
                      items:
                        type: object
        '403':
          description: Forbidden for non-admin users
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/play_moments/{id}":
    delete:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: Delete a play moment
      description: 'Deletes a manual play moment. Auto play moments cannot be deleted
        — they are rejected instead (review transitions to rejected, which updates
        verification accordingly). Provide reviewed_by to attribute the rejection
        in the audit trail.

        '
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                play_moments:
                  type: object
                  properties:
                    reviewed_by:
                      type: string
                      description: Optional. Used as the reviewer when rejecting an
                        auto play moment.
                      example: user123
      responses:
        '200':
          description: Auto play moment rejected
        '204':
          description: Manual play moment deleted
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/play_moments/bulk_delete":
    post:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: Bulk delete play moments at a tick
      description: 'Deletes all manual play moments matching the given annotation_task_id
        and tick. Auto play moments are rejected instead so training data is preserved.
        Optionally filter by verification status. Provide reviewed_by to attribute
        rejections in the audit trail.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - play_moments
              properties:
                play_moments:
                  type: object
                  required:
                  - annotation_task_id
                  - tick
                  properties:
                    annotation_task_id:
                      type: integer
                      description: ID of the annotation task
                      example: 123
                    tick:
                      type: integer
                      description: Tick value to target
                      example: 100
                    verification:
                      type: string
                      enum:
                      - pending
                      - accepted
                      - rejected
                      description: Optional. Only affect play moments with this verification
                        status.
                      example: pending
                    reviewed_by:
                      type: string
                      description: Optional. Used as the reviewer when rejecting auto
                        play moments.
                      example: user123
      responses:
        '200':
          description: Bulk delete completed
        '400':
          description: Bad request - invalid parameters
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/play_moments/bulk_review":
    post:
      security:
      - UserToken: []
      tags:
      - PlayMoments
      summary: Bulk review play moments
      description: Bulk review all play moments in an aggregated group (same annotation_task_id
        and tick)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - play_moments
              properties:
                play_moments:
                  type: object
                  required:
                  - annotation_task_id
                  - tick
                  - action
                  - reviewed_by
                  properties:
                    annotation_task_id:
                      type: integer
                      description: ID of the annotation task
                      example: 123
                    tick:
                      type: integer
                      description: Tick value for the aggregated play moments
                      example: 100
                    action:
                      type: string
                      enum:
                      - accept
                      - reject
                      - reset
                      description: Review action to perform
                      example: accept
                    reviewed_by:
                      type: string
                      description: Identifier of the person performing the review
                        (required for accept/reject, optional for reset)
                      example: user123
                    tags:
                      type: array
                      items:
                        type: string
                      description: Added to the existing tags of each rejected play moment.
                        Ignored for accept and reset.
      responses:
        '200':
          description: Bulk review completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - accept
                    - reject
                    - reset
                    example: accept
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Invalid review action
        '404':
          description: No play moments or reviews found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: No play moments found for the given annotation_task_id
                      and tick
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/users/{user_id}/ratings":
    parameters:
    - name: user_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Player Ratings
      summary: List player ratings
      description: Returns all ratings for the user, including sport and facility
        associations.
      responses:
        '200':
          description: List of ratings
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/PlayerRating"
        '401':
          description: Unauthorized
        '404':
          description: User not found
    post:
      security:
      - UserToken: []
      tags:
      - Player Ratings
      summary: Create a player rating
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - rating
              properties:
                rating:
                  type: object
                  required:
                  - sport_id
                  - rating
                  - source
                  properties:
                    sport_id:
                      type: integer
                    rating:
                      type: number
                    source:
                      type: string
                      enum:
                      - self_rated
                      - facility
                      - dupr
                      - itr
                      - hify
                    facility_id:
                      type: integer
                      nullable: true
                      description: Required when source is "facility"
      responses:
        '201':
          description: Rating created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PlayerRating"
        '401':
          description: Unauthorized
        '404':
          description: User not found
        '422':
          description: Unprocessable entity
  "/v1/users/{user_id}/ratings/{id}":
    parameters:
    - name: user_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: id
      in: path
      required: true
      schema:
        type: integer
    patch:
      security:
      - UserToken: []
      tags:
      - Player Ratings
      summary: Update a player rating
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                rating:
                  type: object
                  properties:
                    rating:
                      type: number
                    source:
                      type: string
                      enum:
                      - self_rated
                      - facility
                      - dupr
                      - itr
                      - hify
                    facility_id:
                      type: integer
                      nullable: true
      responses:
        '200':
          description: Rating updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PlayerRating"
        '401':
          description: Unauthorized
        '404':
          description: User or rating not found
        '422':
          description: Unprocessable entity
  "/v1/me":
    get:
      security:
      - UserToken: []
      tags:
      - Profiles
      summary: Get current user profile
      operationId: getMe
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
      consumes:
      - application/json
      produces:
      - application/json
    delete:
      security:
      - UserToken: []
      tags:
      - Profiles
      summary: Request deletion of the current user's account
      description: 'Records a deletion request against the account and revokes every
        active session, so the caller is signed out on all devices. Signing in again
        within the 7 day cancellation window clears the request. Idempotent — repeating
        the call does not move the requested-at timestamp. Erasure of the underlying
        data is performed separately.

        '
      operationId: deleteMe
      responses:
        '204':
          description: Deletion requested
        '401':
          description: Unauthorized
  "/v1/reviews/accept":
    post:
      security:
      - UserToken: []
      tags:
      - Reviews
      summary: Accept a review (Admin only)
      description: Transitions the review to accepted status and syncs the state to
        the reviewable entity
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - reviewable_type
              - reviewable_id
              - reviewed_by
              properties:
                reviewable_type:
                  type: string
                  description: Type of the reviewable entity
                  example: PlayMoment
                reviewable_id:
                  type: integer
                  description: ID of the reviewable entity
                  example: 42
                reviewed_by:
                  type: string
                  description: Name or identifier of the person reviewing
                  example: jane@test.xyz
      responses:
        '200':
          description: Review accepted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - accepted
                    example: accepted
        '400':
          description: Bad request - parameter missing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - Admin access required
        '404':
          description: Review not found
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/reviews/reject":
    post:
      security:
      - UserToken: []
      tags:
      - Reviews
      summary: Reject a review (Admin only)
      description: Transitions the review to rejected status and syncs the state to
        the reviewable entity
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - reviewable_type
              - reviewable_id
              - reviewed_by
              properties:
                reviewable_type:
                  type: string
                  description: Type of the reviewable entity
                  example: PlayMoment
                reviewable_id:
                  type: integer
                  description: ID of the reviewable entity
                  example: 42
                reviewed_by:
                  type: string
                  description: Name or identifier of the person reviewing
                  example: Jane Smith
      responses:
        '200':
          description: Review rejected successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - rejected
                    example: rejected
        '400':
          description: Bad request - parameter missing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - Admin access required
        '404':
          description: Review not found
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/reviews/reset":
    post:
      security:
      - UserToken: []
      tags:
      - Reviews
      summary: Reset a review (Admin only)
      description: Transitions the review back to pending status and clears reviewed_by
        and reviewed_at fields
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - reviewable_type
              - reviewable_id
              - reviewed_by
              properties:
                reviewable_type:
                  type: string
                  description: Type of the reviewable entity
                  example: PlayMoment
                reviewable_id:
                  type: integer
                  description: ID of the reviewable entity
                  example: 42
                reviewed_by:
                  type: string
                  description: Name or identifier of the person reviewing
                  example: Jane Smith
      responses:
        '200':
          description: Review reset successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - pending
                    example: pending
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - Admin access required
        '404':
          description: Review not found
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/webhooks/jio_delivery_report":
    post:
      summary: Jio delivery report webhook
      security:
      - UserToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
        '501':
          description: Not Implemented
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
  "/v1/sports":
    get:
      security:
      - UserToken: []
      tags:
      - Sports
      summary: List all sports
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of sports
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Sport"
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - Sports
      summary: Create a new sport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                sport:
                  type: object
                  properties:
                    name:
                      type: string
      responses:
        '201':
          description: Sport created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Sport"
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/sports/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      security:
      - UserToken: []
      tags:
      - Sports
      summary: Update a sport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                sport:
                  type: object
                  properties:
                    name:
                      type: string
      responses:
        '200':
          description: Sport updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Sport"
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
    delete:
      security:
      - UserToken: []
      tags:
      - Sports
      summary: Delete a sport
      responses:
        '204':
          description: Sport deleted
  "/v1/statistics":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get combined statistics summary
      deprecated: true
      description: 'Returns momentum, playtime (fixed 1M), and circle-of-players data
        in a single call. All three sections are fetched in parallel. No query params.

        '
      responses:
        '200':
          description: Combined statistics summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  momentum:
                    "$ref": "#/components/schemas/MomentumSummary"
                  playtime:
                    "$ref": "#/components/schemas/PlaytimeSummary"
                  circle:
                    "$ref": "#/components/schemas/CirclePlayers"
  "/v1/statistics/playtime/summary":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get playtime summary for a given period
      deprecated: true
      description: 'Returns playtime statistics for the user. Navigation: pass `navigate=prev`
        or `navigate=next` with the current period''s `from` to paginate through periods.
        Omit both for the current (latest) period.

        '
      parameters:
      - name: period_type
        in: query
        required: false
        schema:
          type: string
          enum:
          - 1M
          - 3M
          - 6M
          default: 3M
        description: Time period duration (default 3M)
      - name: navigate
        in: query
        required: false
        schema:
          type: string
          enum:
          - current
          - prev
          - next
          default: current
        description: Navigation direction relative to the `from` anchor
      - name: from
        in: query
        required: false
        schema:
          type: string
          format: date
        description: 'Anchor date for navigation. Pass the `from` value from the current
          response when navigating prev/next. Not needed for initial load.

          '
      responses:
        '200':
          description: Playtime summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  period:
                    type: object
                    properties:
                      label:
                        type: string
                        example: Jan - Mar '26
                      type:
                        type: string
                        enum:
                        - 1M
                        - 3M
                        - 6M
                      from:
                        type: string
                        format: date
                      to:
                        type: string
                        format: date
                      groupBy:
                        type: string
                      hasNext:
                        type: boolean
                        description: Whether a more recent period is available to
                          navigate to
                      hasPrev:
                        type: boolean
                        description: Whether an older period with data exists to navigate
                          to
                  totals:
                    type: object
                    properties:
                      playMinutes:
                        type: number
                      playMinutesPercentageChange:
                        type: number
                        nullable: true
                        description: Percentage change compared to previous same-length
                          period (can be negative, null if no previous data)
                      sports:
                        type: integer
                      games:
                        type: integer
                      tournaments:
                        type: integer
                  chart:
                    type: object
                    properties:
                      categories:
                        type: array
                        items:
                          type: object
                          properties:
                            label:
                              type: string
                            sports:
                              type: object
                              additionalProperties:
                                type: object
                                properties:
                                  playMinutes:
                                    type: number
                                  tournamentMinutes:
                                    type: number
                                  color:
                                    type: string
                                    example: "#BED747"
  "/v1/statistics/momentum/summary":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get momentum summary
      deprecated: true
      description: 'Returns momentum score and daily trend for a fixed 3-month window
        (current month and two preceding months). No parameters needed.

        '
      responses:
        '200':
          description: Momentum summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  period:
                    type: object
                    properties:
                      label:
                        type: string
                        example: Jan - Mar '26
                      type:
                        type: string
                        enum:
                        - 3M
                      from:
                        type: string
                        format: date
                      to:
                        type: string
                        format: date
                  momentum:
                    type: object
                    properties:
                      score:
                        type: integer
                        description: Current momentum score (0-100)
                      tier:
                        type: string
                        enum:
                        - idle
                        - light
                        - active
                        - intense
                        - maxed
                      tierLabel:
                        type: string
                      typicalRange:
                        type: object
                        description: Q1-Q3 range of daily scores in the period
                        properties:
                          min:
                            type: integer
                          max:
                            type: integer
                      message:
                        type: string
                  range:
                    type: object
                    properties:
                      aggregation:
                        type: string
                        enum:
                        - daily
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            date:
                              type: string
                              format: date
                            score:
                              type: integer
  "/v1/statistics/circle/players":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get circle of players
      responses:
        '200':
          description: List of players in circle
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalPlayers:
                    type: integer
                  players:
                    type: array
                    items:
                      type: object
                      properties:
                        playerId:
                          type: string
                        name:
                          type: string
                        avatarUrl:
                          type: string
                        gamesTogether:
                          type: integer
                        lastPlayedAt:
                          type: string
                          format: date-time
  "/v1/statistics/circle/players/{playerId}":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get games played with a specific player
      deprecated: true
      parameters:
      - name: playerId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Player details and shared games
          content:
            application/json:
              schema:
                type: object
                properties:
                  player:
                    type: object
                    properties:
                      playerId:
                        type: string
                      name:
                        type: string
                      avatarUrl:
                        type: string
                      gamesTogether:
                        type: integer
                  games:
                    type: array
                    items:
                      type: object
                      properties:
                        gameId:
                          type: string
                        sport:
                          type: string
                        startTime:
                          type: string
                          format: date-time
                        endTime:
                          type: string
                          format: date-time
                        facilityName:
                          type: string
                        thumbnailUrl:
                          type: string
                        preview:
                          type: object
                          nullable: true
                          description: Best arena preview for the game; null when
                            none or previews CDN unset.
                          properties:
                            playlistUrl:
                              type: string
                            thumbnailUrl:
                              type: string
                            durationSeconds:
                              type: integer
                              nullable: true
                        tournament:
                          type: object
                          nullable: true
                          description: Present only if the game belongs to a tournament
                          properties:
                            uuid:
                              type: string
                            name:
                              type: string
                            bannerImage:
                              type: string
  "/v1/statistics/leaderboards":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: List available leaderboards (in display order)
      deprecated: true
      description: 'Lists the leaderboards available to the current user, grouped
        by scope in display order — momentum vs circle first, then momentum vs all
        HiFy. Each board carries metadata only (no entries); the sqids share `code`
        is used to load its paginated detail via GET /v1/statistics/leaderboards/{code}.
        No ClickHouse work is done here.

        '
      responses:
        '200':
          description: Grouped available leaderboards
          content:
            application/json:
              schema:
                type: object
                properties:
                  available_leaderboards:
                    type: array
                    items:
                      "$ref": "#/components/schemas/LeaderboardFilterGroup"
  "/v1/statistics/leaderboards/{type}":
    get:
      security:
      - UserToken: []
      tags:
      - Statistics
      summary: Get a single leaderboard (paginated detail view)
      deprecated: true
      description: 'The path segment may be a board type (below) or a sqids share
        `code`; codes that don''t decode return 404.

        '
      parameters:
      - name: type
        in: path
        required: true
        description: Board type (`momentum_global` or `momentum_circle`) or sqids
          share code.
        schema:
          type: string
      - name: page
        in: query
        required: false
        schema:
          type: integer
          default: 1
          minimum: 1
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
          default: 25
          minimum: 1
          maximum: 100
      responses:
        '200':
          description: Paginated leaderboard
          content:
            application/json:
              schema:
                type: object
                properties:
                  leaderboard:
                    "$ref": "#/components/schemas/DetailBoard"
        '404':
          description: Unknown board type or undecodable share code
  "/v1/subscriptions":
    post:
      security:
      - UserToken: []
      summary: Create a subscription and get the gateway checkout payload
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                subscription:
                  type: object
                  required:
                  - plan_price_id
                  properties:
                    plan_price_id:
                      type: integer
      responses:
        '201':
          description: Subscription created; open gateway checkout with `checkout`
          content:
            application/json:
              schema:
                type: object
                properties:
                  subscription:
                    "$ref": "#/components/schemas/Subscription"
                  checkout:
                    type: object
                    description: Opaque payload for the mobile payment SDK
                    example:
                      subscription_id: sub_00000000000001
        '409':
          description: User already has a live subscription
        '404':
          description: Plan price not found
  "/v1/subscriptions/me":
    get:
      security:
      - UserToken: []
      summary: Current subscription, effective plan, and feature allowances
      responses:
        '200':
          description: Subscription state for the current user
          content:
            application/json:
              schema:
                type: object
                properties:
                  subscription:
                    "$ref": "#/components/schemas/Subscription"
                  plan:
                    type: object
                  allowances:
                    type: array
                    items:
                      type: object
                      properties:
                        feature:
                          type: string
                          example: var_review
                        limit_type:
                          type: string
                          enum:
                          - unlimited
                          - quota
                        quota:
                          type: integer
                          nullable: true
                        used:
                          type: integer
                          nullable: true
                        remaining:
                          type: integer
                          nullable: true
  "/v1/subscriptions/{id}/verify-payment":
    post:
      security:
      - UserToken: []
      summary: Verify the checkout signature returned by the gateway SDK
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                subscription:
                  type: object
                  required:
                  - pg_payment_id
                  - pg_payment_signature
                  properties:
                    pg_payment_id:
                      type: string
                    pg_payment_signature:
                      type: string
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: boolean
  "/v1/subscriptions/{id}/cancel":
    post:
      security:
      - UserToken: []
      summary: Cancel at cycle end (entitlement survives until period end)
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Updated subscription
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Subscription"
  "/v1/subscriptions/{id}/charges":
    get:
      security:
      - UserToken: []
      summary: Paginated charge list for one subscription
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: integer
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
      responses:
        '200':
          description: Charges, newest first (X-Total-Count / X-Total-Pages headers)
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    number:
                      type: string
                      example: HIFY-SUB-20260813-1A2B3C4D
                    amount:
                      type: string
                    status:
                      type: string
                      enum:
                      - pending
                      - processing
                      - paid
                      - failed
                      - refunded
                    invoice_url:
                      type: string
                      nullable: true
                    paid_at:
                      type: string
                      format: date-time
                      nullable: true
                    currency_code:
                      type: string
  "/v1/tournaments/{tournament_id}/matches":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Tournament Matches
      summary: List matches for a tournament
      description: 'Returns matches for the tournament, filtered by scope, court number,
        and/or search term. Searches player names (including an anonymous participant''s feed or linked-account name) and side team names. Results are deduplicated and ordered by sequence number.

        '
      parameters:
      - name: scope
        in: query
        required: false
        schema:
          type: string
          enum:
          - past
          - claimed
          - unclaimed
        description: 'Filter matches by scope. "past" returns completed matches the
          current user had claimed. "claimed" returns active matches the current user
          has claimed. "unclaimed" returns non-completed matches with no active claim.
          Default (omitted) returns all non-completed matches.

          '
      - name: court_number
        in: query
        required: false
        schema:
          type: integer
        description: Filter by arena court number (in_facility_id).
      - name: search
        in: query
        required: false
        schema:
          type: string
        description: 'Search by player name (including an anonymous participant''s feed or linked-account
          name) or a side''s team name. Case-insensitive partial match.

          '
      responses:
        '200':
          description: List of matches
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/TournamentMatch"
        '401':
          description: Unauthorized
        '404':
          description: Tournament not found
  "/v1/tournaments/{tournament_id}/scoring_format":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Scoring-format choices for a tournament
      description: 'The active scoring formats for the tournament''s sport, and the
        stage/category slices its matches fall into. eligible_count is how many matches
        in a slice a format change would actually rewrite — matches that have never
        been started and never been scored. Admin or a referee on this tournament.

        '
      responses:
        '200':
          description: Options
          content:
            application/json:
              schema:
                type: object
                properties:
                  scoring_formats:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        slug:
                          type: string
                        name:
                          type: string
                        subtitle:
                          type: string
                          nullable: true
                  slices:
                    type: array
                    items:
                      type: object
                      properties:
                        stage:
                          type: string
                        category:
                          type: string
                          nullable: true
                          description: null is the slice of matches sent without a
                            category
                        match_count:
                          type: integer
                        eligible_count:
                          type: integer
        '403':
          description: Not an admin or a referee on this tournament
        '404':
          description: Tournament not found
    post:
      security:
      - UserToken: []
      tags:
      - Matches
      summary: Apply a scoring format to a stage and category
      description: 'Re-points every match in the slice that has not started and has
        never been scored. In-progress and completed matches keep their format, as
        does a match that was scored and then released — its status reads pending
        but its event log survives. Admin or a referee on this tournament.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - scoring_format
              properties:
                scoring_format:
                  type: object
                  required:
                  - stage
                  - category
                  - scoring_format_id
                  properties:
                    stage:
                      type: string
                      enum:
                      - league
                      - quarterfinal
                      - semifinal
                      - knockout
                      - final
                      - third_place
                    category:
                      type: string
                      nullable: true
                      description: Send null for the uncategorised slice. Required
                        as a key — omitting it, or sending a blank string, is refused
                        rather than defaulting to a slice nobody named.
                    scoring_format_id:
                      type: integer
      responses:
        '200':
          description: Applied
          content:
            application/json:
              schema:
                type: object
                properties:
                  changed:
                    type: integer
                  skipped:
                    type: integer
                    description: Matches in the slice left alone — started, completed,
                      or already scored
        '403':
          description: Not an admin or a referee on this tournament
        '422':
          description: Missing or blank category, unknown stage, or a format belonging
            to another sport
        '404':
          description: Tournament or scoring format not found
  "/v1/tournaments/{tournament_id}/participants":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      security:
      - UserToken: []
      tags:
      - Tournament Participants
      summary: Add a tournament participant
      description: Register a player as a participant. Admin only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - participant
              properties:
                participant:
                  type: object
                  required:
                  - participant_type
                  - participant_id
                  properties:
                    participant_type:
                      type: string
                      enum:
                      - User
                      - AnonymousTournamentParticipant
                    participant_id:
                      type: string
                      format: uuid
                    seed:
                      type: integer
                      description: Seeding rank for bracket placement
      responses:
        '201':
          description: Participant added
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TournamentParticipant"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
        '422':
          description: Unprocessable entity
    get:
      security:
      - UserToken: []
      tags:
      - Tournament Participants
      summary: List the tournament roster
      description: 'Admin only. Every participant — users and the anonymous
        participants a partner feed created — with the name each renders under, ordered
        by id and paged (X-Total-Count / X-Total-Pages).

        '
      parameters:
      - name: participant_type
        in: query
        required: false
        schema:
          type: string
          enum:
          - User
          - AnonymousTournamentParticipant
        description: An unknown value is a 422.
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
        description: Items per page, default 25.
      responses:
        '200':
          description: Participants
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/TournamentParticipant"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
        '422':
          description: Unknown participant_type
  "/v1/tournaments/{tournament_id}/participants/{id}":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: id
      in: path
      required: true
      schema:
        type: integer
    delete:
      security:
      - UserToken: []
      tags:
      - Tournament Participants
      summary: Remove a tournament participant
      description: Admin only.
      responses:
        '204':
          description: Participant removed
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament or participant not found
  "/v1/auth/cable_token":
    post:
      security:
        - UserToken: []
      tags:
        - Cable
      summary: Get a short-lived pass for the live score cable
      description: >
        Browsers cannot set headers on a WebSocket, so they connect to /cable
        with this pass in the URL (`?cable_token=…`) instead of the session
        token. The pass lasts one hour and only works on the cable; fetch a
        new one before connecting once it has expired.
      responses:
        "200":
          description: Pass issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CableToken"
        "401":
          description: Unauthorized
  "/v1/tournaments/follow_along/join":
    post:
      security:
        - UserToken: []
      tags:
        - Tournament Follow Along
      summary: Open a tournament to follow along on a venue TV
      description: >
        Accepts a tournament's viewer code (or, for TVs set up before viewer
        codes, its referee code) and returns what a TV needs
        to follow it live: the tournament, its facility, and each court with
        its time slots. Each slot's game_id is the GameScoreChannel to
        subscribe to while that slot is running. Read-only — the caller does
        not become a referee. Fails if the code has expired.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - follow_along
              properties:
                follow_along:
                  type: object
                  required:
                    - access_code
                  properties:
                    access_code:
                      type: string
                      description: The tournament's viewer code, or for now its referee code (TVs set up before viewer codes), with or without the HIFY- prefix, in any case
      responses:
        "200":
          description: Tournament found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TournamentFollowAlong"
        "400":
          description: Missing, blank or non-text access code
        "401":
          description: Unauthorized
        "404":
          description: Invalid access code — no tournament found
        "422":
          description: Access code has expired
  "/v1/referee/join":
    post:
      security:
      - UserToken: []
      tags:
      - Tournament Referees
      summary: Join a tournament as a referee
      description: 'Accepts an access code to join a tournament as a referee. The
        current authenticated user is registered as a referee for the tournament matching
        the access code. Returns the referee record. Fails if the access code has
        expired.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - referee
              properties:
                referee:
                  type: object
                  required:
                  - access_code
                  properties:
                    access_code:
                      type: string
                      description: Tournament access code (e.g. ABC12), with or without the HIFY- prefix, in any case
      responses:
        '200':
          description: Referee joined successfully
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TournamentReferee"
        '400':
          description: Missing, blank or non-text access code
        '401':
          description: Unauthorized
        '404':
          description: Invalid access code — no tournament found
        '422':
          description: Access code has expired
  "/v1/tournaments/{tournament_id}/sponsors":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Tournament Sponsors
      summary: List sponsors for a tournament
      description: Returns all sponsors for the tournament ordered by display_order.
        Admin only.
      responses:
        '200':
          description: List of tournament sponsors
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/TournamentSponsor"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
    post:
      security:
      - UserToken: []
      tags:
      - Tournament Sponsors
      summary: Create a sponsor for a tournament
      description: Admin only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - tournament_sponsor
              properties:
                tournament_sponsor:
                  type: object
                  required:
                  - position
                  properties:
                    name:
                      type: string
                    position:
                      type: string
                      enum:
                      - top_right
                      - bottom_left
                      - bottom_right
                    display_order:
                      type: integer
                      minimum: 0
                      default: 0
                    s3_bucket:
                      type: string
                    s3_path:
                      type: string
      responses:
        '201':
          description: Sponsor created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TournamentSponsor"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
  "/v1/tournaments/{tournament_id}/sponsors/{id}":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Tournament Sponsors
      summary: Get a tournament sponsor
      description: Admin only.
      responses:
        '200':
          description: Sponsor details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TournamentSponsor"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament or sponsor not found
    patch:
      security:
      - UserToken: []
      tags:
      - Tournament Sponsors
      summary: Update a tournament sponsor
      description: Admin only. Upload a new logo to the path returned by s3_path before
        calling this.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tournament_sponsor:
                  type: object
                  properties:
                    name:
                      type: string
                    position:
                      type: string
                      enum:
                      - top_right
                      - bottom_left
                      - bottom_right
                    display_order:
                      type: integer
                      minimum: 0
                    s3_bucket:
                      type: string
                    s3_path:
                      type: string
      responses:
        '200':
          description: Sponsor updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TournamentSponsor"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament or sponsor not found
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
    delete:
      security:
      - UserToken: []
      tags:
      - Tournament Sponsors
      summary: Delete a tournament sponsor
      description: Admin only. The logo file at s3_path is not automatically removed
        from S3.
      responses:
        '204':
          description: Sponsor deleted
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament or sponsor not found
  "/v1/tournaments":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: List tournaments
      parameters:
      - name: facility_id
        in: query
        schema:
          type: integer
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of tournaments
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    name:
                      type: string
                    start_time:
                      type: string
                      format: date-time
                    end_time:
                      type: string
                      format: date-time
                    banner_image:
                      type: string
                    facility:
                      type: object
                    sport:
                      type: object
                    access_code:
                      type: string
                      description: 'Code a referee enters to join the tournament.
                        Present only for an admin, or a referee on the scorer app;
                        never for a player.

                        '
                    viewer_code:
                      type: string
                      description: Code that opens the tournament's live scores (follow-along). Present only with access_code.
      consumes:
      - application/json
      produces:
      - application/json
    post:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Create tournament
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tournament:
                  type: object
                  properties:
                    name:
                      type: string
                    facility_id:
                      type: string
                    sport_id:
                      type: integer
                    banner_s3_path:
                      type: string
                    start_time:
                      type: string
                      format: date-time
                    end_time:
                      type: string
                      format: date-time
                    scoring_enabled:
                      type: boolean
                      description: Drives the live scorecard overlay for this tournament.
      responses:
        '201':
          description: "Tournament created. access_code, access_code_expires_at and viewer_code are present only for an admin; external_partner_config is never returned."
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/tournaments/{id}":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Get tournament details
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: "Tournament details. access_code, access_code_expires_at and viewer_code are present only for an admin; external_partner_config is never returned."
      consumes:
      - application/json
      produces:
      - application/json
    put:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Update tournament
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tournament:
                  type: object
                  properties:
                    name:
                      type: string
                    facility_id:
                      type: integer
                    sport_id:
                      type: integer
                    banner_s3_path:
                      type: string
                    start_time:
                      type: string
                      format: date-time
                    end_time:
                      type: string
                      format: date-time
                    scoring_enabled:
                      type: boolean
                      description: Drives the live scorecard overlay for this tournament.
      responses:
        '200':
          description: "Tournament updated. access_code, access_code_expires_at and viewer_code are present only for an admin; external_partner_config is never returned."
        '422':
          description: Unprocessable entity
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/tournaments/timeline":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Tournaments the current user has played in, newest first
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Tournament cards with their content counts
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                  - "$ref": "#/components/schemas/TournamentCard"
                  - type: object
                    properties:
                      shorts:
                        "$ref": "#/components/schemas/TournamentContentStat"
                      highlights:
                        "$ref": "#/components/schemas/TournamentContentStat"
                      replays:
                        "$ref": "#/components/schemas/TournamentContentStat"
                      photos:
                        "$ref": "#/components/schemas/TournamentContentStat"
  "/v1/tournaments/{tournament_id}/details":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Tournament details page
      parameters:
      - name: tournament_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The tournament card, the viewer's standing and the content
            sections
          content:
            application/json:
              schema:
                type: object
                properties:
                  tournament_card:
                    allOf:
                    - "$ref": "#/components/schemas/TournamentCard"
                    - type: object
                      properties:
                        arena_video_available:
                          type: boolean
                  needs_selfie:
                    type: boolean
                  can_exit:
                    type: boolean
                  exit:
                    type: object
                  content_state:
                    type: string
                  partial:
                    type: boolean
                  empty_state:
                    type: object
                    nullable: true
                  sections:
                    type: array
                    items:
                      type: object
  "/v1/tournaments/{id}/join/check":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Whether the current user has already joined the tournament
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The tournament card and the viewer's membership
          content:
            application/json:
              schema:
                type: object
                properties:
                  tournament:
                    allOf:
                    - "$ref": "#/components/schemas/TournamentCard"
                    - type: object
                      properties:
                        arena_video_available:
                          type: boolean
                  already_joined:
                    type: boolean
  "/v1/tournaments/{id}/join":
    post:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Join tournament
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tournament:
                  type: object
                  properties:
                    user_photo_s3_path:
                      type: string
                si:
                  type: string
                  format: uuid
                  description: "Invite link's sharer. Used as referrer_id on the join event when it is another user's uuid; otherwise ignored."
      responses:
        '200':
          description: "Successfully joined tournament. Never includes access_code, access_code_expires_at or external_partner_config."
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/tournaments/{id}/photos":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Get tournament photos
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Tournament photos
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/tournaments/{id}/clips":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Get tournament clips
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Tournament clips
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/tournaments/{id}/highlights":
    get:
      security:
      - UserToken: []
      tags:
      - Tournaments
      summary: Get tournament highlights
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: Tournament highlights
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
      consumes:
      - application/json
      produces:
      - application/json
  "/v1/users/{id}":
    patch:
      security:
      - UserToken: []
      tags:
      - Users
      summary: Update a user
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    first_name:
                      type: string
                    last_name:
                      type: string
                    avatar_s3_path:
                      type: string
      responses:
        '200':
          description: User updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  first_name:
                    type: string
                  last_name:
                    type: string
                  avatar_s3_path:
                    type: string
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                type: object
                description: Error messages
    put:
      security:
      - UserToken: []
      tags:
      - Users
      summary: Update a user
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    first_name:
                      type: string
                    last_name:
                      type: string
                    avatar_s3_path:
                      type: string
      responses:
        '200':
          description: User updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  first_name:
                    type: string
                  last_name:
                    type: string
                  avatar_s3_path:
                    type: string
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                type: object
                description: Error messages
  "/v1/edge/var-requests":
    post:
      security:
      - UserToken: []
      tags:
      - Edge VAR Requests
      summary: Submit a Video Assistant Referee request
      description: |
        Accepts a recorded clip URL + camera id, persists a VarRequest, and enqueues a background job that calls the Intelligence DRS service (Modal-hosted, ~30-60s). The response returns immediately with {id, status:"pending"}.
        Clients should then open a WebSocket connection to /cable and subscribe to VarRequestChannel with the returned id to receive every state transition (processing -> completed with playback_url, or failed with error). See the /cable path for the subscription contract.
        The VarRequest is owned by the authenticated user. Only that user can subscribe to its channel.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - var_request
              properties:
                var_request:
                  type: object
                  required:
                  - playlist_url
                  - camera_id
                  properties:
                    playlist_url:
                      type: string
                      description: HLS playlist (m3u8) URL of the recorded clip to
                        analyze.
                      example: https://dbyctxsmb6sh0.cloudfront.net/1248729/292/master.m3u8
                    camera_id:
                      type: string
                      example: '48'
      responses:
        '201':
          description: VAR request accepted and queued for processing.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/VarRequest"
        '400':
          description: Missing var_request key in body.
        '401':
          description: Unauthorized — missing or invalid X-User-Token.
        '403':
          description: Forbidden — caller is not a player.
  "/cable":
    get:
      tags:
      - WebSockets
      summary: ActionCable WebSocket endpoint
      description: |
        WebSocket upgrade endpoint for ActionCable. Authentication uses the same credentials as HTTP: signed session cookie (browsers) or X-User-Token header (native clients). Unauthorized connections are rejected.
        Once connected, subscribe to channels by sending a subscribe command over the socket. Currently supported channel:
        ## VarRequestChannel
        Subscribe payload (sent by client over the socket):
        json {
          "command": "subscribe",
          "identifier": "{\"channel\":\"VarRequestChannel\",\"id\":\"<var_request_id>\"}"
        }

        The channel rejects the subscription if the VarRequest does not exist or is not owned by the authenticated user.
        Broadcasts received (one snapshot per state transition; if the VarRequest is already in a terminal state at subscribe time, the terminal snapshot is transmitted immediately):
        json {"status":"processing"} {"status":"completed","playback_url":"https://...modal.run?run_id=...&filename=overlay.mp4"} {"status":"failed","error":"DRS upstream failed: ..."}

        Clients should unsubscribe once a terminal status (`completed` or `failed`) is received.
      responses:
        '101':
          description: Switching Protocols — WebSocket handshake accepted.
        '401':
          description: Unauthorized — connection rejected.
  "/v1/admin/tournaments/{tournament_id}/matches":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Admin Tournament Matches
      summary: List every match of a tournament for content operations
      description: 'Admin only. Returns all of the tournament''s matches, completed
        included, ordered by game start time, court and sequence number, and paged
        on every request (X-Total-Count / X-Total-Pages). The scorer app''s list at
        /v1/tournaments/{id}/matches is a different view: it hides completed matches
        and scopes claims to the caller.

        '
      parameters:
      - name: date
        in: query
        required: false
        schema:
          type: string
          format: date
        description: 'YYYY-MM-DD. Matches whose game starts on that calendar day at
          the venue (the facility''s country time zone), not in UTC.

          '
      - name: arena_id
        in: query
        required: false
        schema:
          type: integer
        description: Only matches played on this arena.
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - pending
          - in_progress
          - completed
        description: An unknown value is a 422, not an empty list.
      - name: search
        in: query
        required: false
        schema:
          type: string
        description: 'Case-insensitive substring of a player''s first or last name,
          an anonymous participant''s name (feed name or linked account), or a side''s team name.

          '
      - name: page
        in: query
        required: false
        schema:
          type: integer
        description: Page number (1-based).
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
          maximum: 200
        description: Items per page, default 25.
      responses:
        '200':
          description: Matches
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/AdminTournamentMatch"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
        '422':
          description: Unparseable date or unknown status; `errors[0].field` names
            which.
  "/v1/admin/tournaments/{tournament_id}/matches/{id}":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      security:
      - UserToken: []
      tags:
      - Admin Tournament Matches
      summary: One match with its score, rounds and active claim
      description: 'Admin only. The listing row plus the score summary and detail,
        every round, and the scorer currently holding the match. Scoped to the tournament
        in the path: a match of another tournament is a 404.

        '
      responses:
        '200':
          description: Match
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AdminTournamentMatchDetail"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found, or the match is not in it
  "/v1/admin/tournaments/{tournament_id}/games":
    parameters:
    - name: tournament_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Admin Tournament Games
      summary: List every game of a tournament for content operations
      description: 'Admin only. Returns all of the tournament''s games (the slots
        its matches are played in and orders are placed against), ordered by start
        time and court, and paged on every request (X-Total-Count / X-Total-Pages).
        Replaces the participant-scoped GET /v1/games?tournament_id= removed in
        PROD-3212.

        '
      parameters:
      - name: date
        in: query
        required: false
        schema:
          type: string
          format: date
        description: 'YYYY-MM-DD. Games that start on that calendar day at the venue
          (the facility''s country time zone), not in UTC.

          '
      - name: arena_id
        in: query
        required: false
        schema:
          type: integer
        description: Only games played on this arena.
      - name: page
        in: query
        required: false
        schema:
          type: integer
        description: Page number (1-based).
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
          maximum: 200
        description: Items per page, default 25.
      responses:
        '200':
          description: Games
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/AdminTournamentGame"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Tournament not found
        '422':
          description: Unparseable date; `errors[0].field` names it.
  "/v1/admin/import_runs":
    post:
      security:
      - UserToken: []
      tags:
      - Import Runs
      summary: Upload a tournament import on a partner's behalf (Retool)
      description: 'Admin only. Takes the same payload a partner feed sends — players
        and/or matches; a match draw is the matches-only form — and queues it through
        the same pipeline. The partner is resolved to its active schedule_ingest integration;
        the tournament is written to approval_mappings at creation, so the run skips
        the first-run staging a feed needs. The payload is validated before the run
        is created, so a bad one comes back in this response rather than as a failed
        run.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - partner_id
              - tournament_id
              - payload
              properties:
                partner_id:
                  type: integer
                  description: Must have an active schedule_ingest integration.
                tournament_id:
                  type: string
                  format: uuid
                  description: 'The HiFy tournament to apply the payload to. Refused
                    when another partner manages it.

                    '
                payload:
                  description: 'The feed payload, as a JSON object or as its text.
                    Its own tournament_id is the PARTNER''s id for the tournament,
                    not the HiFy one.

                    '
                  oneOf:
                  - type: object
                  - type: string
      responses:
        '201':
          description: The queued run
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ImportRun"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '422':
          description: 'A bad reference (`errors[0].field` names partner_id, tournament_id
            or payload), or an invalid payload (`error: invalid_payload` with the
            importer''s own `{ code, path, message }` list).

            '
        '500':
          description: The payload could not be stored; the run exists as failed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  import_run_id:
                    type: string
                    format: uuid
                  status:
                    type: string
                  error:
                    type: string
                  message:
                    type: string
    get:
      security:
      - UserToken: []
      tags:
      - Import Runs
      summary: List import runs (admin)
      description: 'Admin only. Newest first, paginated. A partner reads its own run
        through GET /v1/import_runs/{id}, which is HMAC-authenticated.

        '
      parameters:
      - name: status
        in: query
        schema:
          type: string
          enum:
          - open
          - in_progress
          - completed
          - failed
          - staged
        description: 'Rejected with 422 if unrecognised, rather than silently returning
          every run.

          '
      - name: kind
        in: query
        schema:
          type: string
        description: 'Not validated — an unknown kind returns an empty list.

          '
      - name: page
        in: query
        schema:
          type: integer
      - name: per_page
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: List of import runs
          headers:
            X-Total-Count:
              schema:
                type: integer
            X-Total-Pages:
              schema:
                type: integer
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/ImportRun"
        '422':
          description: Unrecognised status filter
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        message:
                          type: string
                        field:
                          type: string
                  id:
                    type: string
  "/v1/admin/import_runs/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      security:
      - UserToken: []
      tags:
      - Import Runs
      summary: Read one import run (admin)
      description: 'Admin only; any run, in the same shape as the list. The partner''s
        own read of its run is GET /v1/import_runs/{id}.

        '
      responses:
        '200':
          description: The run
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ImportRun"
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — admin access required
        '404':
          description: Unknown run
  "/v1/admin/import_runs/{id}/approve":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    patch:
      security:
      - UserToken: []
      tags:
      - Import Runs
      summary: Approve a staged import run and re-queue it (admin)
      description: 'Kind-agnostic: stores whatever mappings ops attached, merged into
        those already stored, and re-enqueues the run. Re-approving after fixing something
        upstream needs no body at all.

        '
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                approval_mappings:
                  type: object
                  properties:
                    tournament_id:
                      type: string
                      format: uuid
      responses:
        '200':
          description: Run approved and re-queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
        '409':
          description: 'The run is in_progress or completed and cannot be approved
            — a double-approve must not start two concurrent imports.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  message:
                    type: string
components:
  securitySchemes:
    UserToken:
      type: apiKey
      in: header
      name: X-User-Token
    DeviceToken:
      type: http
      scheme: bearer
      description: 'The recorder Pi''s Device#api_token, delivered to the device in
        its Mender-pushed gateway config. Sent as `Authorization: Bearer <api_token>`.
        This is a device credential, not a user session -- no X-User-Token is involved,
        and the token identifies exactly one Device.

        '
    AccessKeyId:
      type: apiKey
      in: header
      name: X-Access-Key-Id
    Signature:
      type: apiKey
      in: header
      name: X-Signature
    AccessSecret:
      type: apiKey
      in: header
      name: X-Access-Secret
    PartnerAccessKeyId:
      type: apiKey
      in: header
      name: X-Access-Key-Id
      description: Access key id of the recorder_provisioning integration
    PartnerSignature:
      type: apiKey
      in: header
      name: X-Signature
      description: Hex HMAC-SHA256 of the raw request body, keyed with the integration
        secret
  schemas:
    EditorialMedia:
      type: object
      properties:
        id:
          type: integer
          example: 42
        editorial_id:
          type: integer
          example: 7
        position:
          type: integer
          description: Order of the asset within the editorial.
          example: 1
        media_type:
          type: string
          description: Kind of asset.
          example: photo
        s3_bucket:
          type: string
          example: hify-editorials
        s3_path:
          type: string
          description: Bare object key; the CDN host carries the folder segment.
          example: launch/hero.jpg
        thumbnail_s3_path:
          type: string
          nullable: true
          example: launch/hero-thumb.jpg
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Editorial:
      type: object
      properties:
        id:
          type: integer
          example: 7
        title:
          type: string
          example: Launch week
        caption:
          type: string
          nullable: true
          example: What's new this week
        boosted:
          type: boolean
          description: Pins the post to page 1 of the explore feed.
          example: false
        position:
          type: integer
          nullable: true
          description: Ordering among boosted posts; unset positions sort last.
          example: 1
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: Set to retire a post; unset or future means active.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        media:
          type: array
          items:
            "$ref": "#/components/schemas/EditorialMedia"
    EditorialInput:
      type: object
      properties:
        title:
          type: string
          example: Launch week
        caption:
          type: string
          example: What's new this week
        boosted:
          type: boolean
          example: false
        position:
          type: integer
          example: 1
        expires_at:
          type: string
          format: date-time
        editor:
          type: string
          description: Attributed author recorded in the PaperTrail version.
          example: jane.doe
        media:
          type: array
          description: 'The full ordered set of assets. Supplying this replaces the
            editorial''s existing media rather than diffing it.

            '
          items:
            type: object
            properties:
              position:
                type: integer
                example: 1
              media_type:
                type: string
                example: photo
              s3_bucket:
                type: string
                example: hify-editorials
              s3_path:
                type: string
                example: launch/hero.jpg
              thumbnail_s3_path:
                type: string
                example: launch/hero-thumb.jpg
    WinRule:
      type: object
      description: 'How a unit is won. `target` points, games or sets, taken by `margin`.

        '
      properties:
        target:
          type: integer
          example: 6
        margin:
          type: integer
          example: 2
      required:
      - target
    Tiebreak:
      type: object
      description: "`trigger_at` is deliberately independent of the level's own target
        — Fast 6 intercepts at 5-5, a Race to 8 at 7-7.\n"
      properties:
        trigger_at:
          type: integer
          nullable: true
          example: 6
        win:
          "$ref": "#/components/schemas/WinRule"
    Level:
      type: object
      description: 'One level of the score hierarchy. The array runs outermost to
        innermost and its length is what makes a sport hierarchical (set then game,
        as in padel) or flat (game alone, as in pickleball). The last entry is the
        leaf.

        '
      properties:
        type:
          type: string
          example: set
        win:
          "$ref": "#/components/schemas/WinRule"
        tiebreak:
          nullable: true
          allOf:
          - "$ref": "#/components/schemas/Tiebreak"
        scoring:
          type: string
          description: Leaf level only. Which family of point scoring applies.
          enum:
          - tennis_points
          - numeric
        deuce:
          type: object
          description: tennis_points leaves only.
          properties:
            max_advantages:
              type: integer
              nullable: true
              description: 'null unlimited (advantage), 0 golden, 1 silver, 2 star
                point.

                '
              enum:
              -
              - 0
              - 1
              - 2
            receiver_chooses_side:
              type: boolean
            on_set_point:
              type: object
              description: Deuce played on games that decide a set.
                max_advantages is required (null = advantage).
              required:
              - max_advantages
              properties:
                max_advantages:
                  type: integer
                  nullable: true
                  enum:
                  -
                  - 0
                  - 1
                  - 2
                receiver_chooses_side:
                  type: boolean
                  description: Inherits the base deuce when omitted.
            on_match_point:
              type: object
              description: Deuce played on games that decide a match.
                max_advantages is required (null = advantage).
              required:
              - max_advantages
              properties:
                max_advantages:
                  type: integer
                  nullable: true
                  enum:
                  -
                  - 0
                  - 1
                  - 2
                receiver_chooses_side:
                  type: boolean
                  description: Inherits the base deuce when omitted.
        freeze:
          type: object
          description: numeric leaves only.
          nullable: true
          properties:
            enabled:
              type: boolean
            triggers_at:
              type: integer
              nullable: true
        deciding_overrides:
          type: object
          description: 'How the FINAL unit at this level differs. Key presence is
            the signal: omit the object entirely and the decider behaves like any
            other unit; send `{"tiebreak": null}` and it is played out with no tiebreak;
            send a tiebreak and the decider uses that one instead.

            '
          nullable: true
          properties:
            tiebreak:
              nullable: true
              allOf:
              - "$ref": "#/components/schemas/Tiebreak"
      required:
      - type
    Rules:
      type: object
      description: 'The sport-agnostic rule payload. Validated by compiling it, so
        a format that cannot produce a snapshot cannot be saved.

        '
      properties:
        match:
          type: object
          properties:
            win:
              "$ref": "#/components/schemas/WinRule"
            deciding:
              type: object
              nullable: true
              description: 'A decider that REPLACES the final unit — a match tiebreak
                — as distinct from deciding_overrides, which modifies it.

                '
              properties:
                kind:
                  type: string
                  enum:
                  - match_tiebreak
                win:
                  "$ref": "#/components/schemas/WinRule"
                label:
                  type: string
                  nullable: true
                  example: Super tiebreak
          required:
          - win
        levels:
          type: array
          minItems: 1
          items:
            "$ref": "#/components/schemas/Level"
        serve:
          type: object
          properties:
            model:
              type: string
              enum:
              - alternating
              - side_out
              - rally
            player_format:
              type: string
              enum:
              - singles
              - doubles
            starting_score:
              type: string
              nullable: true
              example: 0-0-2
            end_change_at:
              type: integer
              nullable: true
        side_changes:
          type: object
          nullable: true
        extensions:
          type: object
          nullable: true
          description: Sport-specific escape hatch, e.g. the MLP DreamBreaker rotation.
      required:
      - match
      - levels
    ScoringFormat:
      type: object
      properties:
        id:
          type: integer
          example: 12
        sport:
          type: string
          description: 'The sports row, named by its lowercase name. Accepted on write
            and echoed on read. A sport the platform does not have, or has but cannot
            score, is refused.

            '
          enum:
          - padel
          - pickleball
        sport_id:
          type: integer
          readOnly: true
          description: The resolved sports row, returned alongside `sport`.
        slug:
          type: string
          description: Stable identifier. Partner feeds name a format by this.
          example: ipt_race_to_8
        name:
          type: string
          example: IPT Race to 8
        subtitle:
          type: string
          nullable: true
          description: 'Short display name for the referee-facing card. Its presence
            is what makes the compiler emit match_format_info at all.

            '
          example: IPT Race to 8
        description:
          type: string
          nullable: true
        real_world_use:
          type: string
          nullable: true
        rules:
          "$ref": "#/components/schemas/Rules"
        display_overrides:
          type: object
          description: Bespoke copy overriding the generated match_format_info.
        active:
          type: boolean
        rules_version:
          type: integer
          description: 'Bumped on every rules change and stamped into the snapshot,
            so a played match traces to an exact row and revision.

            '
          example: 1
        position:
          type: integer
        scoring_config_snapshot:
          type: object
          description: 'What a device would be given for a match scored under this
            format. Returned on every response so an edit can be confirmed without
            a second request. When the format plays a different deuce on set or
            match point, the snapshot carries a top-level deuce_overrides object
            keyed set_point / match_point, each shaped like deuce. Absent otherwise.

            '
    PreviewError:
      type: object
      properties:
        errors:
          type: array
          items:
            type: string
    Error:
      type: object
      properties:
        kind:
          type: string
          example: ActiveRecord::RecordInvalid
        id:
          type: string
          description: Correlates with the logged error.
        errors:
          type: object
        error:
          type: string
    Camera:
      type: object
      properties:
        id:
          type: string
        arena_id:
          type: string
        in_arena_id:
          type: string
        enabled:
          type: boolean
        noise_cancellation_enabled:
          type: boolean
          description: Opt this camera into measured audio noise cancellation in the
            live encoder.
        brand:
          type: string
        model:
          type: string
        arena_boundary_points:
          type: array
          items:
            type: object
        ip:
          type: string
        http_port:
          type: integer
        rtsp_port:
          type: integer
        usage_types:
          type: array
          items:
            type: string
        arena:
          type: object
          properties:
            id:
              type: string
            facility:
              type: object
              properties:
                id:
                  type: string
    EdgeSoftwareRelease:
      type: object
      properties:
        id:
          type: integer
        artifact_name:
          type: string
        device_type:
          type: string
        latest:
          type: boolean
          description: Rollout pin for automated deploys. Not the newest upload.
    DeviceDeployment:
      type: object
      properties:
        id:
          type: integer
        device_id:
          type: integer
        kind:
          type: string
          enum:
          - config
          - code
        source:
          type: string
          enum:
          - bootstrap
          - manual
          - automatic
        artifact_name:
          type: string
          nullable: true
        mender_deployment_id:
          type: string
          nullable: true
        status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Device:
      type: object
      properties:
        id:
          type: integer
        mender_device_id:
          type: string
          nullable: true
        mac_address:
          type: string
          nullable: true
        status:
          type: string
          enum:
          - pending
          - accepted
          - rejected
          - preauthorized
          - noauth
          - decommissioned
        facility_id:
          type: integer
          nullable: true
        mender_attributes:
          type: object
        upload_mode:
          type: string
          enum:
          - continuous
          - on_demand
        preview_generation_enabled:
          type: boolean
        archive_disk:
          type: boolean
        decommissioned_at:
          type: string
          format: date-time
          nullable: true
        arenas:
          type: array
          items:
            type: object
        facility:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ContentGapDiagnostic:
      type: object
      properties:
        game_id:
          type: integer
          nullable: true
          description: Null in arena window mode.
        game_created_at:
          type: string
          format: date-time
          nullable: true
        facility_id:
          type: integer
        facility_name:
          type: string
        arena_id:
          type: integer
        arena_name:
          type: string
        arena_enabled:
          type: boolean
        start_time:
          type: string
          format: date-time
        end_time:
          type: string
          format: date-time
        final:
          type: boolean
          description: 'False only for `missing` windows that ended less than 12 hours
            ago, whose footage may still arrive.

            '
        bucket:
          type: string
          enum:
          - on_time
          - late
          - missing
        cause:
          type: string
          nullable: true
          enum:
          - no_telemetry
          - wrong_arena
          - analyzer_missed
          - detected_not_ingested
          - upload_outage
          - slow_upload
          - upload_failures
          - unexplained
          -
        delay_minutes:
          type: integer
          nullable: true
          description: First footage arrival minus end_time.
        avm_count:
          type: integer
        preview_count:
          type: integer
        preview_first_arrival_minutes:
          type: integer
          nullable: true
        own_gameplay_decisions:
          type: integer
        own_non_gameplay_decisions:
          type: integer
        top_decision_reasons:
          type: string
          description: Space-separated `reason:count` pairs, most frequent first.
        sibling_arena_id:
          type: integer
          nullable: true
        sibling_avm_count:
          type: integer
          nullable: true
        sibling_gameplay_detected:
          type: boolean
          nullable: true
        booked_siblings_with_avm:
          type: string
          description: Space-separated ids of sibling arenas with footage and their
            own game.
        later_footage_ingested:
          type: boolean
        facility_avg_upload_s_per_segment:
          type: number
          nullable: true
        facility_baseline_upload_s_per_segment:
          type: number
          nullable: true
        facility_upload_failures:
          type: integer
        longest_upload_gap_minutes:
          type: integer
        longest_telemetry_gap_minutes:
          type: integer
        same_slot_game_ids:
          type: string
          description: Space-separated ids of other games overlapping this window
            on the arena.
    FootageRequest:
      type: object
      properties:
        id:
          type: integer
          description: FootageRequest identifier; echo it back on PATCH.
          example: 42
        game_id:
          type: integer
          example: 1287
        arena_id:
          type: integer
          example: 9
        window_start:
          type: string
          format: date-time
          description: Start of the footage window to upload, copied from the Game.
          example: '2026-09-15T18:00:00Z'
        window_end:
          type: string
          format: date-time
          description: End of the footage window to upload, copied from the Game.
          example: '2026-09-15T19:00:00Z'
        status:
          type: string
          enum:
          - pending
          - acknowledged
          - uploading
          description: 'Always an open status here -- terminal requests are omitted
            from the open set.

            '
    DeviceUnauthorized:
      type: object
      properties:
        error:
          type: string
          enum:
          - missing_credentials
          - invalid_token
        message:
          type: string
          example: 'Authorization: Bearer <token> is required'
    Game:
      type: object
      properties:
        id:
          type: integer
        start_time:
          type: string
          format: date-time
        arena:
          "$ref": "#/components/schemas/Arena"
        sport:
          "$ref": "#/components/schemas/Sport"
    Arena:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        game_creation_window_hours:
          type: integer
          description: "How far back, in hours, a game on this arena may start: 72 when any device on the arena is on-demand, else 336 (14 days)."
    GameDetails:
      type: object
      required:
      - game_card
      - needs_selfie
      - can_exit
      - exit
      - content_state
      - partial
      - empty_state
      - sections
      properties:
        game_card:
          "$ref": "#/components/schemas/GameDetailsCard"
        needs_selfie:
          type: boolean
          description: True when the current user still owes a selfie for this game.
        can_exit:
          type: boolean
          description: True when the current user may leave the game.
        exit:
          "$ref": "#/components/schemas/GameParticipantDecision"
        content_state:
          type: string
          enum:
          - scheduled
          - recording
          - content_available
          - content_delayed
          - no_recording
        partial:
          type: boolean
          description: True while an on-demand arena is still backing up this
            game's footage. content_state is then content_available, and sections
            fill in as segments land.
        empty_state:
          nullable: true
          description: Copy to render when content_state is not content_available;
            null otherwise.
          allOf:
          - "$ref": "#/components/schemas/GameDetailsEmptyState"
        sections:
          type: array
          description: Empty unless content_state is content_available. Ordered
            clips, photos, highlights, replays.
          items:
            "$ref": "#/components/schemas/GameDetailsSection"
    GameDetailsCard:
      type: object
      description: The game as shown on timeline cards, with per-content-type counts.
      properties:
        id:
          type: integer
        start_time:
          type: string
          format: date-time
        end_time:
          type: string
          format: date-time
        code:
          type: string
        tournament_id:
          type: string
          nullable: true
        sport:
          "$ref": "#/components/schemas/Sport"
        arena:
          "$ref": "#/components/schemas/Arena"
        game_participants:
          type: array
          description: Current user first, then players before viewers, newest first.
          items:
            "$ref": "#/components/schemas/GameParticipant"
        duration:
          type: number
          description: Seconds between start_time and end_time.
        players_count:
          type: integer
        preview:
          "$ref": "#/components/schemas/GamePreview"
        thumbnail_url:
          type: string
          nullable: true
        arena_video_available:
          type: boolean
        verification_status:
          type: string
          nullable: true
          description: The current user's participant verification, or null when
            they are not a participant.
        shorts:
          "$ref": "#/components/schemas/GameContentStats"
        highlights:
          "$ref": "#/components/schemas/GameContentStats"
        replays:
          "$ref": "#/components/schemas/GameContentStats"
        photos:
          "$ref": "#/components/schemas/GameContentStats"
    GamePreview:
      type: object
      nullable: true
      description: Best arena preview for the game. Null when the game has no preview or the previews CDN is not configured.
      properties:
        playlist_url:
          type: string
          description: HLS playlist URL.
        thumbnail_url:
          type: string
        duration_seconds:
          type: integer
          nullable: true
    GameContentStats:
      type: object
      properties:
        total:
          type: string
          description: Zero-padded count, e.g. "03".
        status:
          type: string
          enum:
          - processing
          - locked
          - unlocked
    GameParticipantDecision:
      type: object
      properties:
        allowed:
          type: boolean
        reason:
          type: string
          nullable: true
          description: Why the action is blocked; null when allowed.
          enum:
          - purchased
          - not_participating
          - null
    GameDetailsEmptyState:
      type: object
      properties:
        title:
          type: string
        message:
          type: string
        icon_url:
          type: string
        eta:
          type: object
          description: Only present for content_delayed, and only when an estimate
            is available.
          properties:
            display_value:
              type: string
              example: ETA TODAY, 8 PM
            eta_seconds:
              type: integer
    GameDetailsSection:
      type: object
      description: One of the content sections. Exactly one of clips, photos,
        highlights or replays is present, matching key.
      required:
      - key
      properties:
        key:
          type: string
          enum:
          - clips
          - photos
          - highlights
          - replays
        clips:
          "$ref": "#/components/schemas/GameDetailsTimeline"
        photos:
          "$ref": "#/components/schemas/GameDetailsTimeline"
        highlights:
          "$ref": "#/components/schemas/GameDetailsTimeline"
        replays:
          "$ref": "#/components/schemas/GameDetailsTimeline"
    GameDetailsTimeline:
      type: object
      properties:
        data:
          type: array
          description: Carousel items for the section. Item shape depends on the
            section and its state (content, sample, sell CTA, view-all cards).
          items:
            type: object
        pagination:
          type: object
          properties:
            current_page:
              type: integer
            per_page:
              type: integer
            total_pages:
              type: integer
            total_count:
              type: integer
        title:
          type: string
          nullable: true
        order_status:
          type: string
          description: Only on the clips section.
          enum:
          - locked
          - processing
          - complete
          - free
          - expired
    Photo:
      type: object
      properties:
        id:
          type: integer
        thumbnail:
          type: string
        display_url:
          type: string
        downloadable_url:
          type: string
    PlayMoment:
      type: object
      properties:
        id:
          type: integer
    Sku:
      type: object
      properties:
        id:
          type: integer
    GameParticipant:
      type: object
      properties:
        id:
          type: integer
        user:
          "$ref": "#/components/schemas/User"
        verification:
          type: string
    User:
      type: object
      properties:
        id:
          type: integer
    Highlight:
      type: object
      properties:
        id:
          type: string
        user_id:
          type: string
        game_id:
          type: string
        order_request_id:
          type: string
        playback_s3_path:
          type: string
        thumbnail_s3_path:
          type: string
    HighlightRequest:
      type: object
      properties:
        id:
          type: string
        user_id:
          type: string
        game_id:
          type: string
        status:
          type: string
    HighlightInput:
      type: object
      properties:
        highlight:
          type: object
          properties:
            user_id:
              type: string
            game_id:
              type: string
            order_request_id:
              type: string
            playback_s3_path:
              type: string
            thumbnail_s3_path:
              type: string
    ImportRun:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          enum:
          - partner_api
          - retool_upload
          description: 'partner_api: sent by the partner''s signed feed. retool_upload:
            submitted by ops from Retool on the partner''s behalf.

            '
        kind:
          type: string
          description: Free-form; grows with each importer. e.g. tournament_import
        status:
          type: string
          enum:
          - open
          - in_progress
          - completed
          - failed
          - staged
        partner_integration_id:
          type: integer
          nullable: true
        importable_type:
          type: string
          nullable: true
        importable_id:
          type: string
          format: uuid
          nullable: true
        row_count:
          type: integer
          nullable: true
        processed_count:
          type: integer
        approval_mappings:
          type: object
          description: 'Ids ops attached when approving, e.g. a tournament_id uuid.

            '
        summary:
          type: object
        pending_mappings:
          type: array
          description: 'Lifted out of summary. What a staged run is waiting for ops
            to attach.

            '
          items:
            type: string
        error_details:
          type: array
          items:
            type: object
        initiated_by_id:
          type: string
          format: uuid
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    LiveStreamDestination:
      type: object
      properties:
        id:
          type: integer
        provider:
          type: string
        account:
          type: string
          enum:
          - hify
          - partner
        stream_protocol:
          type: string
        stream_host:
          type: string
        stream_key:
          type: string
        youtube_broadcast_id:
          type: string
          nullable: true
          description: YouTube liveBroadcast (video) id for a hify destination. Null until the broadcast is created, which happens asynchronously after the live stream is created.
        youtube_watch_url:
          type: string
          nullable: true
          description: Public YouTube page for the stream. Shows the scheduled page before the game, the live stream during it and the replay after. Null until youtube_broadcast_id is set.
        youtube_privacy_status:
          type: string
          enum: [public, unlisted, private]
          description: Visibility of the YouTube broadcast created for a hify destination. Meaningless for a partner destination, which stays public.
    LiveStream:
      type: object
      properties:
        id:
          type: integer
        game_id:
          type: integer
        camera_id:
          type: integer
        game:
          type: object
        camera:
          type: object
        live_stream_destinations:
          type: array
          items:
            "$ref": "#/components/schemas/LiveStreamDestination"
    MatchClaim:
      type: object
      properties:
        id:
          type: integer
        match_id:
          type: integer
        status:
          type: string
          enum:
          - active
          - released
        claimed_at:
          type: string
          format: date-time
        released_at:
          type: string
          format: date-time
          nullable: true
        user:
          type: object
          description: Serialized user object
    MatchSet:
      type: object
      properties:
        id:
          type: integer
        match_id:
          type: integer
        sequence_number:
          type: integer
        winner_side:
          type: string
          enum:
          - side_a
          - side_b
          nullable: true
        score:
          type: object
          nullable: true
    Match:
      type: object
      properties:
        id:
          type: integer
        game_id:
          type: integer
        sequence_number:
          type: integer
          nullable: true
        round_number:
          type: integer
          nullable: true
        stage:
          type: string
          enum:
          - league
          - quarterfinal
          - semifinal
          - knockout
          - final
          - third_place
          description: Where the match sits in the tournament
        status:
          type: string
          enum:
          - pending
          - in_progress
          - completed
        winner_side:
          type: string
          enum:
          - side_a
          - side_b
          - draw
          nullable: true
        first_serve_side:
          type: string
          enum:
          - side_a
          - side_b
          nullable: true
          description: Side that served first; set via start_scoring
        completion_reason:
          type: string
          enum:
          - normal
          - injury
          - walkover
          - other
          nullable: true
          description: 'How the match ended, set from the match_completed event: normal
            for a match played to its conclusion, injury/walkover/other for an early
            end. Null until the match completes.'
        category_label:
          type: string
          nullable: true
          description: Display category, e.g. "Open Mixed Doubles"
        scoring_config_snapshot:
          type: object
          description: The compiled rules the match is scored under, resolved from
            its scoring format (or its tournament's). Empty when neither names one.
        scored_by:
          type: object
          nullable: true
          description: The scorer who last claimed the match, read from the newest
            match_claim regardless of its status. Null when the match was never claimed.
          properties:
            user_id:
              type: string
              format: uuid
            name:
              type: string
              nullable: true
              description: Scorer display name, e.g. "Prajapati D.". Null until the
                scorer has a first/last name on record.
        started_at:
          type: string
          format: date-time
          nullable: true
          description: scored_at of the first non-voided score entry; null until
            the match has been scored.
        ended_at:
          type: string
          format: date-time
          nullable: true
          description: scored_at of the last non-voided score entry; null until
            the match has been scored.
        duration_seconds:
          type: integer
          nullable: true
          description: ended_at minus started_at; null until a second entry exists.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        match_participants:
          type: array
          items:
            "$ref": "#/components/schemas/MatchParticipant"
        team_names:
          type: object
          description: Each side's team name; null for a side without a team.
          properties:
            side_a:
              type: string
              nullable: true
            side_b:
              type: string
              nullable: true
        match_sets:
          type: array
          items:
            "$ref": "#/components/schemas/MatchSet"
        cameras:
          type: array
          description: 'Enabled cameras of the arena where the match is played. Included
            in the show response.

            '
          items:
            type: object
            properties:
              id:
                type: integer
              in_arena_id:
                type: string
        arena:
          type: object
          description: The arena where the match is played, including the device hostname.
          properties:
            id:
              type: integer
            name:
              type: string
            in_facility_id:
              type: integer
            var_hostname:
              type: string
              nullable: true
              description: cloudflare_tunnel_hostname of the arena's device, if any.
    MatchParticipant:
      type: object
      properties:
        id:
          type: integer
        match_id:
          type: integer
        side:
          type: string
          enum:
          - side_a
          - side_b
        participant_type:
          type: string
          enum:
          - User
          - AnonymousTournamentParticipant
        participant_id:
          type: string
          format: uuid
        position:
          type: string
          nullable: true
    OrderRequest:
      type: object
      properties:
        id:
          type: integer
        type:
          type: string
          enum:
          - HighlightRequest
          - ReplayRequest
          - ShortRequest
        status:
          type: string
          enum:
          - open
          - in_progress
          - in_review
          - done
          - archived
        assignee:
          type: string
          nullable: true
          description: Email of the current human owner (annotator, then editor).
            Null when unassigned.
          example: priya@hify.club
        assigned_at:
          type: string
          format: date-time
          nullable: true
        attention_state:
          type: string
          nullable: true
          description: 'Ops-facing pipeline stage group (one group = one owner). Grouped
            from the granular stored/derived states; the granular detail per row stays
            in processing_state and processing_message. Null for rows with no stage
            (e.g. pre-contract replays).

            '
          enum:
          - annotating
          - ready_to_bundle
          - processing
          - needs_editor
          - editing
          - in_review
          - stuck
          - done
        stage_entered_at:
          type: string
          format: date-time
          nullable: true
          description: When the current stage was entered. Poll rewrites do not move
            it.
        processing_state:
          type: string
          nullable: true
          description: Machine-readable stage stored by the owning job. Job-owned;
            not writable via this API.
        processing_details:
          type: object
          description: Structured facts behind processing_state; schema per request
            type.
        processing_message:
          type: string
          nullable: true
          description: Human-readable rendering of processing_state + processing_details.
          example: Delivered 3/12 segments (camera 12); watching for the rest
        next_attempt_at:
          type: string
          format: date-time
          nullable: true
          description: When the system will next act. Null means nothing is scheduled
            (finished, or stuck).
        user:
          type: object
        game:
          type: object
        order:
          type: object
    PartnerMember:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        phonenumber:
          type: string
        email:
          type: string
        department:
          type: string
    Partner:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        address:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        pincode:
          type: string
    PlayerType:
      type: object
      properties:
        type:
          type: string
          description: Player type identifier
        id:
          type: string
          description: Player unique identifier
      required:
      - type
      - id
    PlayerRating:
      type: object
      properties:
        id:
          type: integer
        rating:
          type: number
        source:
          type: string
          enum:
          - self_rated
          - facility
          - dupr
          - itr
          - hify
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        sport:
          type: object
          description: Serialized sport object
        facility:
          type: object
          nullable: true
          description: Serialized facility object (null when source is not facility)
    Review:
      type: object
      properties:
        id:
          type: integer
        reviewable_type:
          type: string
          example: PlayMoment
        reviewable_id:
          type: integer
        status:
          type: string
          enum:
          - pending
          - accepted
          - rejected
        reviewed_by:
          type: string
          nullable: true
        reviewed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TournamentContentStat:
      type: object
      properties:
        total:
          type: string
          description: Zero-padded count, e.g. "04"
        status:
          type: string
          enum:
          - processing
          - locked
          - unlocked
    TournamentCard:
      type: object
      description: A tournament as the app's cards show it (Home::TournamentSerializer).
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        start_time:
          type: string
          format: date-time
          nullable: true
        end_time:
          type: string
          format: date-time
          nullable: true
        banner_url:
          type: string
          nullable: true
        logo_url:
          type: string
          nullable: true
        participants:
          type: array
          description: The viewer first, then players, newest first
          items:
            "$ref": "#/components/schemas/GameParticipant"
        facility:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
        scoring_enabled:
          type: boolean
        sport:
          "$ref": "#/components/schemas/Sport"
    Sport:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
    MomentumSummary:
      type: object
      properties:
        period:
          type: object
          properties:
            label:
              type: string
            type:
              type: string
              enum:
              - 3M
            from:
              type: string
              format: date
            to:
              type: string
              format: date
        momentum:
          type: object
          properties:
            score:
              type: integer
            tier:
              type: string
              enum:
              - idle
              - light
              - active
              - intense
              - maxed
            tierLabel:
              type: string
            typicalRange:
              type: object
              properties:
                min:
                  type: integer
                max:
                  type: integer
            message:
              type: string
        range:
          type: object
          properties:
            aggregation:
              type: string
              enum:
              - daily
            data:
              type: array
              items:
                type: object
                properties:
                  date:
                    type: string
                    format: date
                  score:
                    type: integer
    PlaytimeSummary:
      type: object
      properties:
        period:
          type: object
          properties:
            label:
              type: string
            type:
              type: string
              enum:
              - 1M
              - 3M
              - 6M
            from:
              type: string
              format: date
            to:
              type: string
              format: date
            groupBy:
              type: string
            hasNext:
              type: boolean
            hasPrev:
              type: boolean
        totals:
          type: object
          properties:
            playMinutes:
              type: number
            playMinutesPercentageChange:
              type: number
              nullable: true
            sports:
              type: integer
            games:
              type: integer
            tournaments:
              type: integer
        chart:
          type: object
          properties:
            categories:
              type: array
              items:
                type: object
                properties:
                  label:
                    type: string
                  sports:
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        playMinutes:
                          type: number
                        tournamentMinutes:
                          type: number
                        color:
                          type: string
                          example: "#BED747"
    CirclePlayers:
      type: object
      properties:
        totalPlayers:
          type: integer
        players:
          type: array
          items:
            type: object
            properties:
              playerId:
                type: string
              name:
                type: string
              avatarUrl:
                type: string
              gamesTogether:
                type: integer
              lastPlayedAt:
                type: string
                format: date-time
    Momentum:
      type: object
      properties:
        score:
          type: number
        tier:
          type: string
          enum:
          - idle
          - light
          - active
          - intense
          - maxed
        tierLabel:
          type: string
    Trend:
      type: object
      properties:
        period:
          type: string
          enum:
          - 7d
        change:
          type: string
          nullable: true
    LeaderboardEntry:
      type: object
      properties:
        rank:
          type: integer
          nullable: true
        user:
          type: object
        score:
          type: number
        momentum:
          "$ref": "#/components/schemas/Momentum"
        isCurrentUser:
          type: boolean
        trend:
          "$ref": "#/components/schemas/Trend"
    LeaderboardFilterBoard:
      type: object
      description: Board metadata for a selectable leaderboard — no entries.
      properties:
        type:
          type: string
          enum:
          - momentum_circle
          - momentum_global
        code:
          type: string
          description: Sqids share code for the detail endpoint
        name:
          type: string
          example: My circle
        backgroundText:
          type: string
          example: Momentum
    LeaderboardFilterGroup:
      type: object
      description: A scope group of selectable leaderboards (entries omitted).
      properties:
        scope:
          type: object
          properties:
            type:
              type: string
              enum:
              - momentum
            value:
              type: string
              example: Momentum leaderboards
        backgroundText:
          type: string
          example: Momentum
        boards:
          type: array
          items:
            "$ref": "#/components/schemas/LeaderboardFilterBoard"
    Pagination:
      type: object
      properties:
        currentPage:
          type: integer
        perPage:
          type: integer
        totalCount:
          type: integer
        totalPages:
          type: integer
    DetailBoard:
      type: object
      properties:
        type:
          type: string
          enum:
          - momentum_global
          - momentum_circle
        code:
          type: string
          description: Sqids share code for this board
        name:
          type: string
        backgroundText:
          type: string
        currentUser:
          "$ref": "#/components/schemas/LeaderboardEntry"
        entries:
          type: array
          items:
            "$ref": "#/components/schemas/LeaderboardEntry"
        pagination:
          "$ref": "#/components/schemas/Pagination"
    Subscription:
      type: object
      nullable: true
      properties:
        id:
          type: integer
        status:
          type: string
          enum:
          - created
          - authenticated
          - active
          - past_due
          - halted
          - cancelled
          - completed
        pg:
          type: string
          example: razorpay
        current_period_start:
          type: string
          format: date-time
          nullable: true
        current_period_end:
          type: string
          format: date-time
          nullable: true
        cancel_at_period_end:
          type: boolean
        payment_method_summary:
          type: object
    TournamentMatch:
      type: object
      properties:
        id:
          type: integer
        game_id:
          type: integer
        sequence_number:
          type: integer
          nullable: true
        round_number:
          type: integer
          nullable: true
        stage:
          type: string
          enum:
          - league
          - quarterfinal
          - semifinal
          - knockout
          - final
          - third_place
          description: Where the match sits in the tournament
        status:
          type: string
          enum:
          - pending
          - in_progress
          - completed
        status_label:
          type: string
          enum:
          - UPCOMING
          - LIVE
          - DONE
        winner_side:
          type: string
          enum:
          - side_a
          - side_b
          - draw
          nullable: true
        court_number:
          type: integer
          nullable: true
        game_start_time:
          type: string
          format: date-time
          nullable: true
        game_end_time:
          type: string
          format: date-time
          nullable: true
        category_label:
          type: string
          nullable: true
          description: Display category, e.g. "Open Mixed Doubles"
        score_summary:
          type: string
          nullable: true
        score_detail:
          type: object
          nullable: true
        winner_names:
          type: string
          nullable: true
        team_names:
          type: object
          description: Each side's team name; null for a side without a team.
          properties:
            side_a:
              type: string
              nullable: true
            side_b:
              type: string
              nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        duration_seconds:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        match_participants:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              match_id:
                type: integer
              side:
                type: string
                enum:
                - side_a
                - side_b
              participant_type:
                type: string
                enum:
                - User
                - AnonymousTournamentParticipant
              participant_id:
                type: string
        match_sets:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              match_id:
                type: integer
              sequence_number:
                type: integer
              winner_side:
                type: string
                enum:
                - side_a
                - side_b
                nullable: true
              score:
                type: object
                nullable: true
    TournamentParticipant:
      type: object
      properties:
        id:
          type: integer
        tournament_id:
          type: string
          format: uuid
        participant_type:
          type: string
          enum:
          - User
          - AnonymousTournamentParticipant
        participant_id:
          type: string
          format: uuid
        seed:
          type: integer
          nullable: true
        name:
          type: string
          nullable: true
          description: The name the participant renders under.
        external_id:
          type: string
          nullable: true
          description: 'The partner''s own player id, for an AnonymousTournamentParticipant
            a feed created. Null for a User.

            '
        linked_user_id:
          type: string
          format: uuid
          nullable: true
          description: 'The account an anonymous participant was linked to when the
            feed''s phone number found one. Null for a User.

            '
    CableToken:
      type: object
      properties:
        cable_token:
          type: string
        expires_in:
          type: integer
          description: Seconds until the pass stops working
          example: 3600
    TournamentFollowAlong:
      type: object
      properties:
        viewer_code:
          type: string
          description: The code for watching scores; a client keeps it in place of the code it sent.
        tournament:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
        facility:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            time_zone:
              type: string
              example: Asia/Kolkata
            icon_url:
              type: string
              nullable: true
        courts:
          type: array
          description: Courts with a game in the tournament, ordered by in_facility_id
          items:
            type: object
            properties:
              in_facility_id:
                type: integer
              name:
                type: string
                description: Court name, or "Court <in_facility_id>" when unnamed
              slots:
                type: array
                description: The court's tournament games, ordered by start_time
                items:
                  type: object
                  properties:
                    game_id:
                      type: integer
                    start_time:
                      type: string
                      format: date-time
                    end_time:
                      type: string
                      format: date-time
    TournamentReferee:
      type: object
      properties:
        id:
          type: integer
        tournament_id:
          type: integer
        user:
          type: object
          description: Serialized user object
    TournamentSponsor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tournament_id:
          type: string
          format: uuid
        name:
          type: string
        s3_bucket:
          type: string
          description: S3 bucket that holds the sponsor logo, derived from media_assets_bucket
            setting
        s3_path:
          type: string
          description: S3 key path for the logo (e.g. /tournaments/{id}/sponsors/logos/{id}.jpeg)
        position:
          type: string
          enum:
          - top_right
          - bottom_left
          - bottom_right
        display_order:
          type: integer
          minimum: 0
    VarRequest:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: VarRequest identifier; used as the subscription id on the VarRequestChannel.
        status:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
        playback_url:
          type: string
          nullable: true
          description: Rendered overlay video URL. Present only when status is "completed".
        error:
          type: string
          nullable: true
          description: Failure reason (sourced from VarRequest.error_message). Present
            only when status is "failed".
    AdminMatchParticipant:
      type: object
      properties:
        id:
          type: integer
        participant_type:
          type: string
          enum:
          - User
          - AnonymousTournamentParticipant
        participant_id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
          description: The name the participant renders under.
    AdminOrderRequest:
      type: object
      properties:
        id:
          type: integer
        type:
          type: string
          description: HighlightRequest, ReplayRequest or ShortRequest.
        status:
          type: string
          enum:
          - open
          - in_progress
          - in_review
          - done
          - archived
    AdminTournamentGame:
      type: object
      description: 'One row of the content-ops game table: the slot, its court and
        the orders placed against it. No participants or media.

        '
      properties:
        id:
          type: integer
        code:
          type: string
          description: The join code of the game.
        tournament_id:
          type: string
          format: uuid
        sport_id:
          type: integer
          nullable: true
        arena_id:
          type: integer
        start_time:
          type: string
          format: date-time
        end_time:
          type: string
          format: date-time
        court_number:
          type: integer
          nullable: true
          description: The arena's in_facility_id.
        sport:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
        arena:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
            in_facility_id:
              type: integer
        order_requests:
          type: array
          description: Every order placed against this slot, any status.
          items:
            type: object
            properties:
              id:
                type: integer
              type:
                type: string
                enum:
                - HighlightRequest
                - ReplayRequest
                - ShortRequest
              status:
                type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AdminTournamentMatch:
      type: object
      description: 'One row of the content-ops match table. Lighter than the scorer
        app''s Match: no resolved scoring config, claim epoch or cameras.

        '
      properties:
        id:
          type: integer
        external_id:
          type: string
          nullable: true
          description: The partner's match id when the match came from a feed.
        game_id:
          type: integer
        sequence_number:
          type: integer
          nullable: true
        round_number:
          type: integer
          nullable: true
        stage:
          type: string
          enum:
          - league
          - quarterfinal
          - semifinal
          - knockout
          - final
          - third_place
        category:
          type: string
          nullable: true
          description: Display label, e.g. "Open Mixed Doubles".
        status:
          type: string
          enum:
          - pending
          - in_progress
          - completed
        winner_side:
          type: string
          nullable: true
          enum:
          - side_a
          - side_b
          - draw
        completion_reason:
          type: string
          nullable: true
        scoring_format_id:
          type: integer
          nullable: true
        court_number:
          type: integer
          nullable: true
          description: The arena's in_facility_id.
        started_at:
          type: string
          format: date-time
          nullable: true
          description: 'scored_at of the match''s first non-voided score entry, the
            same value the scorer app''s Match.started_at carries. Null until the
            match has been scored. The game''s start_time is the slot; this is the
            match.

            '
        ended_at:
          type: string
          format: date-time
          nullable: true
          description: scored_at of the match's last non-voided score entry. Null
            until the match has been scored.
        sides:
          type: object
          description: Both keys are always present.
          properties:
            side_a:
              type: array
              items:
                "$ref": "#/components/schemas/AdminMatchParticipant"
            side_b:
              type: array
              items:
                "$ref": "#/components/schemas/AdminMatchParticipant"
        team_names:
          type: object
          description: Each side's team name; null for a side without a team.
          properties:
            side_a:
              type: string
              nullable: true
            side_b:
              type: string
              nullable: true
        game:
          type: object
          description: The slot the match is bound to and the orders placed against
            it.
          properties:
            id:
              type: integer
            start_time:
              type: string
              format: date-time
            end_time:
              type: string
              format: date-time
            arena:
              type: object
              nullable: true
              properties:
                id:
                  type: integer
                name:
                  type: string
                in_facility_id:
                  type: integer
            order_requests:
              type: array
              items:
                "$ref": "#/components/schemas/AdminOrderRequest"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AdminMatchRound:
      type: object
      properties:
        id:
          type: integer
        match_id:
          type: integer
        round_type:
          type: string
        sequence_number:
          type: integer
        parent_round_id:
          type: integer
          nullable: true
        status:
          type: string
        winner_side:
          type: string
          nullable: true
        score_a:
          type: integer
        score_b:
          type: integer
        closed_at:
          type: string
          format: date-time
          nullable: true
        closed_by:
          type: string
          nullable: true
    AdminTournamentMatchDetail:
      allOf:
      - "$ref": "#/components/schemas/AdminTournamentMatch"
      - type: object
        description: 'The row plus how the match stands. The score is resolved per
          match, which is why it is not on the listing.

          '
        properties:
          score_summary:
            type: string
            nullable: true
            description: Sets won, e.g. "2–1"; games for a race format.
          score_detail:
            type: string
            nullable: true
            description: Per-set games, e.g. "6-4, 3-6, 7-5".
          active_claim:
            type: object
            nullable: true
            description: The scorer holding the match right now, if any.
            properties:
              id:
                type: integer
              user_id:
                type: string
                format: uuid
              device_id:
                type: string
                nullable: true
          match_rounds:
            type: array
            items:
              "$ref": "#/components/schemas/AdminMatchRound"
security:
- UserToken: []
servers:
- url: "/"
