openapi: 3.1.0
info:
  title: MirrorMingo Developer API
  version: 1.1.0
  summary: Exam preparation and private Arena competitions.
  description: "The **MirrorMingo Exam API** is the public, developer-facing surface for\nMirrorMingo's\
    \ verified exam-prep platform. It exposes:\n\n- A registry of supported exams (JAMB, WAEC, NECO, IELTS,\
    \ SAT, and more),\n  each with official section specs, scoring rubrics and verified source links.\n\
    - A library of **real, never-fabricated past-question papers**, grouped by year.\n- A timed **mock-exam\
    \ engine**: open a paper, page through questions,\n  submit answers, and receive scored feedback.\n\
    \nAll content is curriculum-grounded and verified against official exam-board\nsources. The Exam API\
    \ does not generate past questions. Arena accepts partner-supplied questions, which are not automatically\
    \ verified by MirrorMingo.\n\nMirrorMingo School Intelligence is a separate school-subscription product.\n\
    It is not exposed through this third-party developer API.\n\n## Authentication\n\nEvery request must\
    \ include a MirrorMingo API key as a bearer token:\n\n```\nAuthorization: Bearer mirrormingo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\
    ```\n\nCreate and manage keys from the Developer dashboard. Keys are shown only\nonce at creation\
    \ time — store them securely and never embed them in\nclient-side code.\n\n## Versioning\n\nThe API\
    \ is versioned in the path (`/v1`). Breaking changes ship under a new\nmajor version; additive changes\
    \ (new fields, new endpoints) may appear\nwithin `v1` without notice, so clients should ignore unknown\
    \ fields.\n\n## Rate limits\n\nRequests are rate limited per API key. The current allowance is returned\
    \ on\nevery response via the `RateLimit-*` headers. On exhaustion the API responds\nwith `429 Too\
    \ Many Requests` and a `Retry-After` header.\n"
  contact:
    name: MirrorMingo Developer Support
    url: https://developers.mirrormingo.com
    email: developers@mirrormingo.com
  license:
    name: Proprietary
    url: https://developers.mirrormingo.com/terms
servers:
- url: https://api.mirrormingo.com/v1
  description: Production
- url: https://sandbox.api.mirrormingo.com/v1
  description: Sandbox (test keys, non-billable)
security:
- apiKey: []
tags:
- name: Exams
  description: Discover supported exams and their official specifications.
- name: Mock Exams
  description: Open verified past-question papers and run timed attempts.
- name: Arena
  description: Private competitions, scoped learner sessions and durable completion events.
paths:
  /exam-prep/list:
    get:
      operationId: listExams
      summary: List supported exams
      description: 'Returns the catalog of exams MirrorMingo supports, optionally filtered

        by country. Each entry carries enough metadata to render a picker.

        '
      tags:
      - Exams
      parameters:
      - name: country
        in: query
        required: false
        description: Full country name to prioritise locally relevant exams (e.g. `Nigeria`).
        schema:
          type: string
      - name: country_code
        in: query
        required: false
        description: ISO 3166-1 alpha-2 country code (e.g. `NG`).
        schema:
          type: string
          minLength: 2
          maxLength: 2
      responses:
        '200':
          description: The exam catalog.
          content:
            application/json:
              schema:
                type: object
                required:
                - exams
                properties:
                  exams:
                    type: array
                    items:
                      $ref: '#/components/schemas/ExamSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/overview:
    get:
      operationId: getExamOverview
      summary: Get exam overview
      description: Full specification for one exam — sections, timing, question types and scoring.
      tags:
      - Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      responses:
        '200':
          description: The exam specification.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExamOverview'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/resources:
    get:
      operationId: getExamResources
      summary: Get verified exam resources
      description: Official exam-board sites and verified past-question sources for the exam.
      tags:
      - Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      responses:
        '200':
          description: Verified resource links.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExamResources'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/study-path:
    get:
      operationId: getExamStudyPath
      summary: Get a recommended study path
      description: A suggested week-by-week study path for the exam.
      tags:
      - Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      responses:
        '200':
          description: The recommended study path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExamStudyPath'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/mock-exams:
    get:
      operationId: listMockPapers
      summary: List available past-question papers
      description: 'Lists the verified past-question papers available for an exam, grouped

        by year. Use a `paper_id` from this response to open a mock attempt.

        '
      tags:
      - Mock Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      responses:
        '200':
          description: Available papers, grouped by year.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MockPaperCatalog'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/mock-exams/open:
    post:
      operationId: openMockPaper
      summary: Open a paper into a timed attempt
      description: 'Opens a verified paper into a new timed mock-exam attempt and returns the

        first page of questions. The returned `attempt_id` is used to page through

        questions and to submit answers.

        '
      tags:
      - Mock Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenMockPaperRequest'
      responses:
        '200':
          description: The new attempt and its first page of questions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MockAttempt'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/mock-exams/questions:
    post:
      operationId: getMockQuestionPage
      summary: Load a page of questions
      description: Loads one page of questions for a paper, for paging through long papers.
      tags:
      - Mock Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MockQuestionPageRequest'
      responses:
        '200':
          description: A page of questions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MockQuestionPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/mock-exams/submit:
    post:
      operationId: submitMockAttempt
      summary: Submit a mock attempt
      description: Submits learner answers for an attempt and returns scored feedback.
      tags:
      - Mock Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitMockAttemptRequest'
      responses:
        '200':
          description: Submission accepted; scored feedback returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MockAttemptResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /exam-prep/{exam_key}/mock-exams/history:
    get:
      operationId: listMockAttempts
      summary: List past attempts
      description: Lists prior mock-exam attempts for the authenticated key, newest first.
      tags:
      - Mock Exams
      parameters:
      - $ref: '#/components/parameters/ExamKey'
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 50
          default: 10
      - name: offset
        in: query
        schema:
          type: integer
          minimum: 0
          default: 0
      - name: learner_id
        in: query
        required: false
        description: Stable learner reference from your app. MirrorMingo stores only a hash-derived internal
          namespace.
        schema:
          type: string
          maxLength: 160
      responses:
        '200':
          description: A page of attempts.
          content:
            application/json:
              schema:
                type: object
                required:
                - attempts
                - total
                properties:
                  attempts:
                    type: array
                    items:
                      $ref: '#/components/schemas/MockAttemptSummary'
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /arena/competitions:
    post:
      tags:
      - Arena
      summary: Create Competition
      operationId: createArenaCompetition
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArenaCreate'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      security:
      - ArenaBearer: []
      description: Create Competition. See the Arena integration guide for lifecycle and retry semantics.
  /arena/competitions/{match_id}:
    get:
      tags:
      - Arena
      summary: Get Competition
      operationId: getArenaCompetition
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Get Competition. See the Arena integration guide for lifecycle and retry semantics.
  /arena/competitions/{match_id}/sessions:
    post:
      tags:
      - Arena
      summary: Create Session
      operationId: createArenaSession
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArenaSessionInput'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSession'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Create Session. See the Arena integration guide for lifecycle and retry semantics.
  /arena/competitions/{match_id}/start:
    post:
      tags:
      - Arena
      summary: Start Competition
      operationId: startArenaCompetition
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Start Competition. See the Arena integration guide for lifecycle and retry semantics.
  /arena/competitions/{match_id}/cancel:
    post:
      tags:
      - Arena
      summary: Cancel Competition
      operationId: cancelArenaCompetition
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Cancel Competition. See the Arena integration guide for lifecycle and retry semantics.
  /arena/competitions/{match_id}/revoke-sessions:
    post:
      tags:
      - Arena
      summary: Revoke Sessions
      operationId: revokeArenaSessions
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaRevocation'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Revoke Sessions. See the Arena integration guide for lifecycle and retry semantics.
  /arena/play/{match_id}:
    get:
      tags:
      - Arena
      summary: Play State
      operationId: getArenaState
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Play State. See the Arena integration guide for lifecycle and retry semantics.
  /arena/play/{match_id}/answers:
    post:
      tags:
      - Arena
      summary: Answer
      operationId: submitArenaAnswer
      security:
      - ArenaBearer: []
      parameters:
      - name: match_id
        in: path
        required: true
        schema:
          type: string
          title: Match Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArenaAnswer'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaSnapshot'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Answer. See the Arena integration guide for lifecycle and retry semantics.
  /arena/events:
    get:
      tags:
      - Arena
      summary: Events
      operationId: listArenaEvents
      security:
      - ArenaBearer: []
      parameters:
      - name: after
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
          default: 0
          title: After
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          maximum: 100
          minimum: 1
          default: 50
          title: Limit
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArenaEventPage'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '401':
          description: Invalid or expired credential
        '403':
          description: Credential lacks permission
        '404':
          description: Resource missing or not owned by this account
        '409':
          description: State or request ID conflict
        '429':
          description: Rate limited; respect Retry-After
      description: Events. See the Arena integration guide for lifecycle and retry semantics.
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: mirrormingo_<hex>
      description: A MirrorMingo API key issued from the Developer dashboard.
    ArenaBearer:
      type: http
      scheme: bearer
  parameters:
    ExamKey:
      name: exam_key
      in: path
      required: true
      description: The exam identifier (e.g. `jamb`, `waec`, `ielts`).
      schema:
        type: string
        minLength: 1
  responses:
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: The requested exam, paper or attempt does not exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Rate limit exceeded for this API key.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: A short machine-readable error code or message.
        detail:
          type: string
          description: A human-readable explanation, when available.
    ExamSummary:
      type: object
      required:
      - exam_key
      - name
      properties:
        exam_key:
          type: string
          examples:
          - jamb
        name:
          type: string
          examples:
          - Joint Admissions and Matriculation Board
        category:
          type: string
          examples:
          - university_admission
        country:
          type: string
          examples:
          - Nigeria
        duration_min:
          type: integer
          examples:
          - 120
    ExamSection:
      type: object
      properties:
        name:
          type: string
        duration_min:
          type: integer
        questions:
          type: integer
        question_types:
          type: array
          items:
            type: string
        skills:
          type: array
          items:
            type: string
    ExamOverview:
      type: object
      required:
      - exam_key
      - name
      properties:
        exam_key:
          type: string
        name:
          type: string
        category:
          type: string
        country:
          type: string
        duration_min:
          type: integer
        sections:
          type: array
          items:
            $ref: '#/components/schemas/ExamSection'
        scoring:
          type: object
          additionalProperties: true
          description: Scoring rubric and band descriptors (shape varies by exam).
    ExamResources:
      type: object
      properties:
        exam_key:
          type: string
        official_sites:
          type: array
          items:
            $ref: '#/components/schemas/ResourceLink'
        past_questions_sources:
          type: array
          items:
            $ref: '#/components/schemas/ResourceLink'
    ResourceLink:
      type: object
      properties:
        title:
          type: string
        url:
          type: string
          format: uri
    ExamStudyPath:
      type: object
      properties:
        exam_key:
          type: string
        study_weeks:
          type: integer
        study_hours_total:
          type: number
        study_path:
          type: array
          items:
            type: object
            additionalProperties: true
    MockPaperCatalog:
      type: object
      required:
      - exam_key
      - total_papers
      - years
      properties:
        exam_key:
          type: string
        total_papers:
          type: integer
        years:
          type: array
          items:
            type: object
            required:
            - year
            - papers
            properties:
              year:
                type: integer
                examples:
                - 2023
              papers:
                type: array
                items:
                  $ref: '#/components/schemas/MockPaperSummary'
    MockPaperSummary:
      type: object
      required:
      - paper_id
      properties:
        paper_id:
          type: string
          examples:
          - jamb-mathematics-2023
        title:
          type: string
          examples:
          - JAMB Mathematics 2023
        subject:
          type: string
          examples:
          - Mathematics
        year:
          type: integer
        question_count:
          type: integer
    OpenMockPaperRequest:
      type: object
      required:
      - paper_id
      properties:
        paper_id:
          type: string
          minLength: 6
          maxLength: 160
          description: A `paper_id` from the mock-exam catalog.
        question_offset:
          type: integer
          minimum: 0
          default: 0
        question_limit:
          type: integer
          minimum: 1
          maximum: 20
          default: 9
        learner_id:
          type: string
          maxLength: 160
          description: Stable learner reference from your app. Use the same value when paging, submitting
            and listing history.
    MockQuestionPageRequest:
      type: object
      required:
      - paper_id
      properties:
        paper_id:
          type: string
          minLength: 6
          maxLength: 160
        question_offset:
          type: integer
          minimum: 0
          default: 0
        question_limit:
          type: integer
          minimum: 1
          maximum: 20
          default: 9
        learner_id:
          type: string
          maxLength: 160
          description: Same learner reference used when opening the attempt, if any.
    MockQuestion:
      type: object
      required:
      - question_number
      - prompt
      properties:
        answer_id:
          type: string
          description: Stable key to use in SubmitMockAttemptRequest.answers for this question.
          example: '12:4'
        question_number:
          type: integer
        prompt:
          type: string
        question_text:
          type: string
          description: Backward-compatible alias for prompt.
        options:
          type: object
          description: Answer options keyed by letter. Absent for non-MCQ questions.
          additionalProperties:
            type: string
          examples:
          - A: '12'
            B: '14'
            C: '16'
            D: '18'
        page:
          type: integer
        has_visual:
          type: boolean
          description: True if the question references a figure or diagram.
    MockAttempt:
      type: object
      required:
      - attempt_id
      - paper_id
      - question_count
      - questions
      properties:
        attempt_id:
          type: string
        paper_id:
          type: string
        question_count:
          type: integer
        questions:
          type: array
          items:
            $ref: '#/components/schemas/MockQuestion'
    MockQuestionPage:
      type: object
      required:
      - questions
      - total
      properties:
        questions:
          type: array
          items:
            $ref: '#/components/schemas/MockQuestion'
        total:
          type: integer
    SubmitMockAttemptRequest:
      type: object
      required:
      - attempt_id
      - answers
      properties:
        attempt_id:
          type: string
          minLength: 6
          maxLength: 160
        answers:
          type: object
          description: Map of `answer_id` to chosen option letter. Plain question-number keys are accepted
            for compatibility when unambiguous.
          additionalProperties:
            type: string
          examples:
          - '12:1': C
            '12:2': A
            '12:3': D
        status:
          type: string
          enum:
          - submitted
          - expired
          - abandoned
          default: submitted
        learner_id:
          type: string
          maxLength: 160
          description: Same learner reference used when opening the attempt, if any.
    MockAttemptResult:
      type: object
      required:
      - success
      properties:
        success:
          type: boolean
        feedback:
          type: object
          additionalProperties: true
          description: Score, per-question correctness and explanations.
    MockAttemptSummary:
      type: object
      properties:
        attempt_id:
          type: string
        paper_id:
          type: string
        score:
          type: number
        submitted_at:
          type: string
          format: date-time
    ArenaAnswer:
      properties:
        request_id:
          type: string
          maxLength: 100
          minLength: 1
          title: Request Id
        question_id:
          type: string
          maxLength: 100
          minLength: 1
          title: Question Id
        picked_index:
          type: integer
          maximum: 5.0
          minimum: 0.0
          title: Picked Index
      additionalProperties: false
      type: object
      required:
      - request_id
      - question_id
      - picked_index
      title: ArenaAnswer
    ArenaCreate:
      properties:
        request_id:
          type: string
          maxLength: 100
          minLength: 1
          title: Request Id
        title:
          type: string
          maxLength: 200
          minLength: 1
          title: Title
        questions:
          items:
            $ref: '#/components/schemas/ArenaQuestion'
          type: array
          maxItems: 120
          minItems: 1
          title: Questions
        max_players:
          type: integer
          maximum: 6.0
          minimum: 1.0
          title: Max Players
          default: 6
        seconds_per_question:
          type: integer
          maximum: 300.0
          minimum: 10.0
          title: Seconds Per Question
          default: 30
        opens_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Opens At
        closes_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Closes At
      additionalProperties: false
      type: object
      required:
      - request_id
      - title
      - questions
      title: ArenaCreate
    ArenaEvent:
      properties:
        sequence:
          type: integer
          title: Sequence
        event_id:
          type: string
          title: Event Id
        type:
          type: string
          title: Type
        match_id:
          type: string
          title: Match Id
        data:
          additionalProperties: true
          type: object
          title: Data
      type: object
      required:
      - sequence
      - event_id
      - type
      - match_id
      - data
      title: ArenaEvent
    ArenaEventPage:
      properties:
        events:
          items:
            $ref: '#/components/schemas/ArenaEvent'
          type: array
          title: Events
        cursor:
          type: integer
          title: Cursor
        has_more:
          type: boolean
          title: Has More
      type: object
      required:
      - events
      - cursor
      - has_more
      title: ArenaEventPage
    ArenaPlayer:
      properties:
        id:
          type: string
          title: Id
        display_name:
          type: string
          title: Display Name
        score:
          type: integer
          title: Score
        answered:
          type: integer
          title: Answered
        elapsed_seconds:
          type: number
          title: Elapsed Seconds
        rank:
          anyOf:
          - type: integer
          - type: 'null'
          title: Rank
      type: object
      required:
      - id
      - display_name
      - score
      - answered
      - elapsed_seconds
      title: ArenaPlayer
    ArenaQuestion:
      properties:
        id:
          type: string
          maxLength: 100
          minLength: 1
          title: Id
        prompt:
          type: string
          maxLength: 2000
          minLength: 1
          title: Prompt
        options:
          items:
            type: string
          type: array
          maxItems: 6
          minItems: 2
          title: Options
        correct_index:
          type: integer
          maximum: 5.0
          minimum: 0.0
          title: Correct Index
        explanation:
          type: string
          maxLength: 4000
          title: Explanation
          default: ''
      additionalProperties: false
      type: object
      required:
      - id
      - prompt
      - options
      - correct_index
      title: ArenaQuestion
    ArenaResult:
      properties:
        question_id:
          type: string
          title: Question Id
        participant_id:
          type: string
          title: Participant Id
        picked_index:
          anyOf:
          - type: integer
          - type: 'null'
          title: Picked Index
        correct_index:
          type: integer
          title: Correct Index
        explanation:
          type: string
          title: Explanation
        points:
          type: integer
          title: Points
      type: object
      required:
      - question_id
      - participant_id
      - picked_index
      - correct_index
      - explanation
      - points
      title: ArenaResult
    ArenaRevocation:
      properties:
        revoked:
          type: boolean
          title: Revoked
      type: object
      required:
      - revoked
      title: ArenaRevocation
    ArenaSession:
      properties:
        token:
          type: string
          title: Token
        match_id:
          type: string
          title: Match Id
        participant_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Participant Id
        role:
          type: string
          enum:
          - participant
          - spectator
          title: Role
        expires_at:
          type: string
          title: Expires At
        websocket_path:
          type: string
          title: Websocket Path
      type: object
      required:
      - token
      - match_id
      - participant_id
      - role
      - expires_at
      - websocket_path
      title: ArenaSession
    ArenaSessionInput:
      properties:
        role:
          type: string
          enum:
          - participant
          - spectator
          title: Role
          default: participant
        learner_id:
          type: string
          maxLength: 160
          title: Learner Id
          default: ''
        display_name:
          type: string
          maxLength: 100
          title: Display Name
          default: ''
        ttl_seconds:
          type: integer
          maximum: 86400.0
          minimum: 60.0
          title: Ttl Seconds
          default: 3600
      additionalProperties: false
      type: object
      title: ArenaSessionInput
    ArenaSnapshot:
      properties:
        id:
          type: string
          title: Id
        title:
          type: string
          title: Title
        version:
          type: integer
          title: Version
        ruleset:
          type: string
          const: turns-v1
          title: Ruleset
          default: turns-v1
        phase:
          type: string
          enum:
          - waiting
          - running
          - reveal
          - finished
          - cancelled
          title: Phase
        server_time:
          type: number
          title: Server Time
        deadline:
          anyOf:
          - type: number
          - type: 'null'
          title: Deadline
        current_index:
          type: integer
          title: Current Index
        question_count:
          type: integer
          title: Question Count
        max_players:
          type: integer
          title: Max Players
        seconds_per_question:
          type: integer
          title: Seconds Per Question
        players:
          items:
            $ref: '#/components/schemas/ArenaPlayer'
          type: array
          title: Players
        turn_player_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Turn Player Id
        question:
          anyOf:
          - $ref: '#/components/schemas/ArenaVisibleQuestion'
          - type: 'null'
        result:
          anyOf:
          - $ref: '#/components/schemas/ArenaResult'
          - type: 'null'
      type: object
      required:
      - id
      - title
      - version
      - phase
      - server_time
      - deadline
      - current_index
      - question_count
      - max_players
      - seconds_per_question
      - players
      - turn_player_id
      - question
      - result
      title: ArenaSnapshot
    ArenaVisibleQuestion:
      properties:
        id:
          type: string
          title: Id
        prompt:
          type: string
          title: Prompt
        options:
          items:
            type: string
          type: array
          title: Options
      type: object
      required:
      - id
      - prompt
      - options
      title: ArenaVisibleQuestion
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
            - type: string
            - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
      - loc
      - msg
      - type
      title: ValidationError
