openapi: 3.1.0
info:
  title: MirrorMingo Exam API
  version: 1.0.0
  summary: Verified African exam content & timed mock-exam engine.
  description: |
    The **MirrorMingo Exam API** is the public, developer-facing surface for
    MirrorMingo's verified exam-prep platform. It exposes:

    - A registry of supported exams (JAMB, WAEC, NECO, IELTS, SAT, and more),
      each with official section specs, scoring rubrics and verified source links.
    - A library of **real, never-fabricated past-question papers**, grouped by year.
    - A timed **mock-exam engine**: open a paper, page through questions,
      submit answers, and receive scored feedback.

    All content is curriculum-grounded and verified against official exam-board
    sources. This API never returns AI-fabricated questions.

    ## Authentication

    Every request must include a MirrorMingo API key as a bearer token:

    ```
    Authorization: Bearer mirrormingo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```

    Create and manage keys from the Developer dashboard. Keys are shown only
    once at creation time — store them securely and never embed them in
    client-side code.

    ## Versioning

    The API is versioned in the path (`/v1`). Breaking changes ship under a new
    major version; additive changes (new fields, new endpoints) may appear
    within `v1` without notice, so clients should ignore unknown fields.

    ## Rate limits

    Requests are rate limited per API key. The current allowance is returned on
    every response via the `RateLimit-*` headers. On exhaustion the API responds
    with `429 Too Many Requests` and a `Retry-After` header.
  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.

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 }
      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" }

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: mirrormingo_<hex>
      description: A MirrorMingo API key issued from the Developer dashboard.

  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

    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 }

    MockQuestion:
      type: object
      required: [question_number, prompt]
      properties:
        question_number: { type: integer }
        prompt: { type: string }
        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 question number (as string) to chosen option letter.
          additionalProperties: { type: string }
          examples:
            - { "1": "C", "2": "A", "3": "D" }
        status:
          type: string
          enum: [submitted, expired, abandoned]
          default: submitted

    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 }
