openapi: 3.0.3
info:
  title: Smartschool OneRoster 1.1 API
  description: |
    Read-only OneRoster 1.1 REST API exposed by Smartschool for external data consumers (e.g. LTI tools, SIS integrations).

    All list endpoints support common query parameters for filtering and pagination.
    Every response includes the `X-Total-Count` header with the total number of records
    before pagination was applied.

    Authentication uses OAuth 2.0 Client Credentials flow. Obtain a bearer token from
    `/ims/oneroster/token` and pass it as `Authorization: Bearer <token>` on every request.
  version: 1.1.0
  contact:
    name: Smartschool
    url: https://www.smartschool.be

servers:
  - url: https://{subdomain}.smartschool.be/ims/oneroster
    description: Smartschool school instance
    variables:
      subdomain:
        default: demo
        description: School-specific subdomain

tags:
  - name: Auth
    description: OAuth 2.0 token endpoint
  - name: Orgs
    description: Organisations and schools
  - name: Classes
    description: Classes (groups / courses)
  - name: Users
    description: Users, students and teachers
  - name: Enrollments
    description: Enrollment relationships between users and classes
  - name: Courses
    description: Course catalogue
  - name: AcademicSessions
    description: School years and academic sessions

# ---------------------------------------------------------------------------
# Security
# ---------------------------------------------------------------------------
security:
  - oauth2ClientCredentials:
      - https://purl.imsglobal.org/spec/or/v1p1/scope/roster-core.readonly

components:

  # -------------------------------------------------------------------------
  # Security schemes
  # -------------------------------------------------------------------------
  securitySchemes:
    oauth2ClientCredentials:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: /token
          scopes:
            https://purl.imsglobal.org/spec/or/v1p1/scope/roster-core.readonly: >
              Read-only access to the full OneRoster 1.1 roster data set.

  # -------------------------------------------------------------------------
  # Reusable parameters
  # -------------------------------------------------------------------------
  parameters:
    filter:
      name: filter
      in: query
      required: false
      description: |
        OneRoster filter expression. Supported values:

        | Expression | Effect |
        |---|---|
        | `status='active'` | Return only active records |
        | `status='norecords'` | Return only deleted / inactive records |
        | `username='<value>'` | Filter users by username |
        | `title='<value>'` | Filter classes / sessions by title |
        | `email='<value>'` | Filter users by e-mail address |
        | `role='student'` | Enrollments only — return student enrollments |
        | `role='teacher'` | Enrollments only — return teacher enrollments |
      schema:
        type: string
        example: "status='active'"

    offset:
      name: offset
      in: query
      required: false
      description: Zero-based index of the first record to return. Pagination is only applied when both `offset` **and** `limit` are present.
      schema:
        type: integer
        minimum: 0
        default: 0

    limit:
      name: limit
      in: query
      required: false
      description: Maximum number of records to return. Pagination is only applied when both `offset` **and** `limit` are present.
      schema:
        type: integer
        minimum: 1

    schoolId:
      name: schoolId
      in: path
      required: true
      description: Numeric platform identifier of the school / org.
      schema:
        type: string

    classId:
      name: classId
      in: path
      required: true
      description: OneRoster `sourcedId` of the class.
      schema:
        type: string

    studentId:
      name: studentId
      in: path
      required: true
      description: OneRoster `sourcedId` of the student (equals the user's internal identifier).
      schema:
        type: string

    teacherId:
      name: teacherId
      in: path
      required: true
      description: OneRoster `sourcedId` of the teacher.
      schema:
        type: string

    termId:
      name: termId
      in: path
      required: true
      description: OneRoster `sourcedId` of the academic session / term.
      schema:
        type: string

  # -------------------------------------------------------------------------
  # Reusable headers
  # -------------------------------------------------------------------------
  headers:
    X-Total-Count:
      description: Total number of records matching the filter **before** pagination was applied.
      schema:
        type: integer
        minimum: 0

  # -------------------------------------------------------------------------
  # Schemas
  # -------------------------------------------------------------------------
  schemas:

    # --- Org / School -------------------------------------------------------
    Org:
      type: object
      required:
        - sourcedId
        - status
        - name
        - type
        - identifier
      properties:
        sourcedId:
          type: string
          description: Unique platform identifier (numeric string).
          example: "42"
        status:
          type: string
          enum: [active]
          description: Orgs are always reported as active.
        name:
          type: string
          description: Human-readable school name.
          example: "Smartschool Demo School"
        type:
          type: string
          enum: [school]
        identifier:
          type: string
          description: Prefixed platform identifier (`platform_{id}`).
          example: "platform_42"

    # --- AcademicSession ----------------------------------------------------
    AcademicSession:
      type: object
      required:
        - sourcedId
        - status
        - title
        - startDate
        - endDate
        - type
        - schoolYear
      properties:
        sourcedId:
          type: string
          description: Composite key `{platformId}_{schoolyearDateInBetween}`.
          example: "42_2024-09-01"
        status:
          type: string
          enum: [active, tobedeleted]
        title:
          type: string
          description: School year label. Characters `/`, `*`, `?`, `"`, `\`, `<`, `>`, `(`, `)`, `|` are replaced with `_`; `:` is replaced with ` - `.
          example: "2024 - 2025"
        startDate:
          type: string
          format: date
          description: Start date of the school year (`Y-m-d`).
          example: "2024-09-01"
        endDate:
          type: string
          format: date
          description: End date of the school year (`Y-m-d`).
          example: "2025-06-30"
        type:
          type: string
          enum: [schoolYear]
        schoolYear:
          type: string
          description: Calendar year of the end date (`Y` format).
          example: "2025"

    # --- Course -------------------------------------------------------------
    Course:
      type: object
      required:
        - sourcedId
        - status
        - title
        - courseCode
      properties:
        sourcedId:
          type: string
          description: Composite key `{platformId}_{courseId}`.
          example: "42_101"
        status:
          type: string
          enum: [active, tobedeleted]
        title:
          type: string
          description: Full course title.
          example: "Mathematics"
        courseCode:
          type: string
          description: Short course code.
          example: "MATH"

    # --- ClassTerm (embedded in Class) -------------------------------------
    ClassTerm:
      type: object
      required:
        - sourcedId
        - status
        - title
        - startDate
        - endDate
        - type
      properties:
        sourcedId:
          type: string
          description: Composite key `{platformId}_{schoolyearDateInBetween}`.
          example: "42_2024-09-01"
        status:
          type: string
          enum: [active, tobedeleted]
        title:
          type: string
          description: School year label.
          example: "2024-2025"
        startDate:
          type: string
          format: date
          example: "2024-09-01"
        endDate:
          type: string
          format: date
          example: "2025-06-30"
        type:
          type: string
          enum: [schoolYear]

    # --- ClassMetadata ------------------------------------------------------
    ClassMetadata:
      type: object
      required:
        - smsc.classType
      properties:
        smsc.classType:
          type: string
          description: Internal Smartschool class type identifier.
          example: "officialClass"
        smsc.instituteNumber:
          type: string
          description: Official institute number (only for official classes).
          example: "123456"
        smsc.studyYear:
          type: string
          description: Natural study year (only for official classes).
          example: "3"
        smsc.parentIdentifier:
          type: string
          description: OneRoster `sourcedId` of the parent group (only for official classes when a parent exists).
          example: "42_usermanagement_99"
        smsc.legacyIdentifier:
          type: string
          description: DES-EDE3 + Base64 encrypted group identifier for backwards compatibility.
          example: "a2xhc3MxMjM0NTY="

    # --- Class --------------------------------------------------------------
    Class:
      type: object
      required:
        - sourcedId
        - status
        - title
        - classCode
        - classType
        - school
        - terms
        - metadata
      properties:
        sourcedId:
          type: string
          description: Composite key `{platformId}_{sourceType}_{classId}`.
          example: "42_usermanagement_7"
        status:
          type: string
          enum: [active, tobedeleted]
        title:
          type: string
          description: Full class name.
          example: "3A Mathematics"
        classCode:
          type: string
          description: Short class code.
          example: "3AMATH"
        classType:
          type: string
          enum: [scheduled]
        school:
          $ref: '#/components/schemas/Org'
        terms:
          type: array
          description: Academic sessions this class belongs to (always exactly one entry).
          items:
            $ref: '#/components/schemas/ClassTerm'
        metadata:
          $ref: '#/components/schemas/ClassMetadata'

    # --- ClassWithType (embedded in Enrollment) ----------------------------
    ClassWithType:
      allOf:
        - $ref: '#/components/schemas/Class'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum: [class]

    # --- UserMetadata -------------------------------------------------------
    UserMetadata:
      type: object
      required:
        - smsc.legacyIdentifier
      properties:
        smsc.legacyIdentifier:
          type: string
          description: DES-EDE3 + Base64 encrypted user identifier for backwards compatibility.
          example: "dXNlcjEyMzQ1Njc4"
        smsc.classInfo:
          type: string
          description: Full name of the student's official class (students only).
          example: "3A"
        smsc.classYear:
          type: string
          description: Natural study year of the student's class (students only).
          example: "3"
        smsc.classLevel:
          type: string
          description: Education type / level of the student's class (students only).
          example: "ASO"
        smsc.classNumber:
          type: string
          description: Administrative group number of the student's class (students only).
          example: "7"
        smsc.internalNumber:
          type: string
          description: School-internal student number (students only, optional).
          example: "S2024001"

    # --- User ---------------------------------------------------------------
    User:
      type: object
      required:
        - sourcedId
        - identifier
        - status
        - username
        - givenName
        - familyName
        - email
        - role
        - sex
        - orgs
        - metadata
      properties:
        sourcedId:
          type: string
          description: Internal user identifier (numeric string).
          example: "1001"
        identifier:
          type: string
          description: Username of the user.
          example: "jdoe"
        status:
          type: string
          enum: [active, tobedeleted]
        username:
          type: string
          description: Username (same as `identifier`).
          example: "jdoe"
        givenName:
          type: string
          description: First name.
          example: "John"
        familyName:
          type: string
          description: Last name.
          example: "Doe"
        email:
          type: string
          format: email
          example: "john.doe@school.be"
        role:
          type: string
          enum: [student, teacher]
        sex:
          type: string
          enum: [male, female]
        orgs:
          type: array
          description: The school(s) the user belongs to (always exactly one entry).
          items:
            $ref: '#/components/schemas/Org'
        birthDate:
          type: string
          format: date
          description: Date of birth (`Y-m-d`). Present for students only, when available.
          example: "2010-05-14"
        metadata:
          $ref: '#/components/schemas/UserMetadata'

    # --- UserWithType (embedded in Enrollment) ----------------------------
    UserWithType:
      allOf:
        - $ref: '#/components/schemas/User'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum: [user]

    # --- Enrollment ---------------------------------------------------------
    Enrollment:
      type: object
      required:
        - sourcedId
        - status
        - role
        - primary
        - user
        - class
        - school
      properties:
        sourcedId:
          type: string
          description: Composite key `{userId}.{classId}`.
          example: "1001.42_usermanagement_7"
        status:
          type: string
          enum: [active, tobedeleted]
        role:
          type: string
          enum: [student, teacher]
        primary:
          type: boolean
          description: |
            Whether this is the user's primary enrollment in this class.
            **Note:** the value is stored inverted in the database — `true` here means the DB flag is `false`.
        user:
          $ref: '#/components/schemas/UserWithType'
        class:
          $ref: '#/components/schemas/ClassWithType'
        school:
          $ref: '#/components/schemas/Org'

    # --- Error --------------------------------------------------------------
    AccessDenied:
      type: object
      properties:
        message:
          type: string
          example: "Access Denied."

  # -------------------------------------------------------------------------
  # Reusable responses
  # -------------------------------------------------------------------------
  responses:
    Unauthorized:
      description: |
        The request lacks valid authentication credentials.
        Possible causes: missing token, expired token, revoked client, inactive client,
        or missing `roster-core.readonly` scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AccessDenied'

    NotFound:
      description: |
        The requested resource does not exist.
        Returns an empty object (single) or empty array (list) with `X-Total-Count: 0`.
      headers:
        X-Total-Count:
          $ref: '#/components/headers/X-Total-Count'

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
paths:

  # =========================================================================
  # Auth
  # =========================================================================
  /token:
    post:
      operationId: obtainToken
      summary: Obtain OAuth 2.0 bearer token
      tags: [Auth]
      security: []   # no auth needed to obtain a token
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - grant_type
                - client_id
                - client_secret
              properties:
                grant_type:
                  type: string
                  enum: [client_credentials]
                client_id:
                  type: string
                  description: OAuth2 client ID issued by Smartschool.
                client_secret:
                  type: string
                  format: password
                  description: OAuth2 client secret issued by Smartschool.
                scope:
                  type: string
                  description: Requested scope.
                  example: "https://purl.imsglobal.org/spec/or/v1p1/scope/roster-core.readonly"
      responses:
        "200":
          description: Token issued successfully.
          content:
            application/json:
              schema:
                type: object
                required:
                  - access_token
                  - token_type
                  - expires_in
                properties:
                  access_token:
                    type: string
                  token_type:
                    type: string
                    enum: [Bearer]
                  expires_in:
                    type: integer
                    description: Token lifetime in seconds.
        "401":
          $ref: '#/components/responses/Unauthorized'

  # =========================================================================
  # Orgs (alias: schools)
  # =========================================================================
  /v1p1/orgs:
    get:
      operationId: getAllOrgs
      summary: List all orgs
      description: Returns all organisations (schools) accessible to the authenticated client. Identical to `GET /v1p1/schools`.
      tags: [Orgs]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of orgs.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [orgs]
                properties:
                  orgs:
                    type: array
                    items:
                      $ref: '#/components/schemas/Org'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/orgs/{schoolId}:
    get:
      operationId: getOrg
      summary: Get org by ID
      description: Returns a single organisation by its numeric platform ID. Identical to `GET /v1p1/schools/{schoolId}`.
      tags: [Orgs]
      parameters:
        - $ref: '#/components/parameters/schoolId'
      responses:
        "200":
          description: The requested org.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [org]
                properties:
                  org:
                    $ref: '#/components/schemas/Org'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /v1p1/schools:
    get:
      operationId: getAllSchools
      summary: List all schools
      description: Returns all schools accessible to the authenticated client. Identical to `GET /v1p1/orgs`.
      tags: [Orgs]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of schools.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [orgs]
                properties:
                  orgs:
                    type: array
                    items:
                      $ref: '#/components/schemas/Org'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/schools/{schoolId}:
    get:
      operationId: getSchool
      summary: Get school by ID
      description: Returns a single school by its numeric platform ID. Identical to `GET /v1p1/orgs/{schoolId}`.
      tags: [Orgs]
      parameters:
        - $ref: '#/components/parameters/schoolId'
      responses:
        "200":
          description: The requested school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [org]
                properties:
                  org:
                    $ref: '#/components/schemas/Org'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  # =========================================================================
  # Classes
  # =========================================================================
  /v1p1/classes:
    get:
      operationId: getAllClasses
      summary: List all classes
      tags: [Classes]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of classes.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [classes]
                properties:
                  classes:
                    type: array
                    items:
                      $ref: '#/components/schemas/Class'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/classes/{classId}:
    get:
      operationId: getClass
      summary: Get class by ID
      tags: [Classes]
      parameters:
        - $ref: '#/components/parameters/classId'
      responses:
        "200":
          description: The requested class.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [class]
                properties:
                  class:
                    $ref: '#/components/schemas/Class'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /v1p1/schools/{schoolId}/classes:
    get:
      operationId: getClassesBySchool
      summary: List classes by school
      description: |
        Returns classes for the given school. If `schoolId` does not match the authenticated
        platform, an empty list is returned with `X-Total-Count: 0`.
      tags: [Classes]
      parameters:
        - $ref: '#/components/parameters/schoolId'
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of classes for the school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [classes]
                properties:
                  classes:
                    type: array
                    items:
                      $ref: '#/components/schemas/Class'
        "401":
          $ref: '#/components/responses/Unauthorized'

  # =========================================================================
  # Users
  # =========================================================================
  /v1p1/users:
    get:
      operationId: getAllUsers
      summary: List all users
      description: Returns all users (students and teachers combined).
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of users.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [users]
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/students:
    get:
      operationId: getAllStudents
      summary: List all students
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of students.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [users]
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/students/{studentId}:
    get:
      operationId: getStudent
      summary: Get student by ID
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/studentId'
      responses:
        "200":
          description: The requested student.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [user]
                properties:
                  user:
                    $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /v1p1/schools/{schoolId}/students:
    get:
      operationId: getStudentsBySchool
      summary: List students by school
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/schoolId'
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of students for the school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [users]
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/teachers:
    get:
      operationId: getAllTeachers
      summary: List all teachers
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of teachers.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [users]
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/teachers/{teacherId}:
    get:
      operationId: getTeacher
      summary: Get teacher by ID
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/teacherId'
      responses:
        "200":
          description: The requested teacher.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [user]
                properties:
                  user:
                    $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /v1p1/schools/{schoolId}/teachers:
    get:
      operationId: getTeachersBySchool
      summary: List teachers by school
      tags: [Users]
      parameters:
        - $ref: '#/components/parameters/schoolId'
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of teachers for the school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [users]
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        "401":
          $ref: '#/components/responses/Unauthorized'

  # =========================================================================
  # Enrollments
  # =========================================================================
  /v1p1/enrollments:
    get:
      operationId: getAllEnrollments
      summary: List all enrollments
      description: |
        Returns all enrollment relationships between users and classes.

        The `filter` parameter additionally supports:
        - `role='student'` — return only student enrollments
        - `role='teacher'` — return only teacher enrollments
      tags: [Enrollments]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of enrollments.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [enrollments]
                properties:
                  enrollments:
                    type: array
                    items:
                      $ref: '#/components/schemas/Enrollment'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/schools/{schoolId}/enrollments:
    get:
      operationId: getEnrollmentsBySchool
      summary: List enrollments by school
      description: |
        Returns enrollments for the given school. If `schoolId` does not match the
        authenticated platform, an empty list is returned with `X-Total-Count: 0`.

        Supports the same `role='student'` / `role='teacher'` filter values as the global endpoint.
      tags: [Enrollments]
      parameters:
        - $ref: '#/components/parameters/schoolId'
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of enrollments for the school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [enrollments]
                properties:
                  enrollments:
                    type: array
                    items:
                      $ref: '#/components/schemas/Enrollment'
        "401":
          $ref: '#/components/responses/Unauthorized'

  # =========================================================================
  # Courses
  # =========================================================================
  /v1p1/courses:
    get:
      operationId: getAllCourses
      summary: List all courses
      tags: [Courses]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of courses.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [courses]
                properties:
                  courses:
                    type: array
                    items:
                      $ref: '#/components/schemas/Course'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/schools/{schoolId}/courses:
    get:
      operationId: getCoursesBySchool
      summary: List courses by school
      tags: [Courses]
      parameters:
        - $ref: '#/components/parameters/schoolId'
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of courses for the school.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [courses]
                properties:
                  courses:
                    type: array
                    items:
                      $ref: '#/components/schemas/Course'
        "401":
          $ref: '#/components/responses/Unauthorized'

  # =========================================================================
  # Academic Sessions
  # =========================================================================
  /v1p1/academicSessions:
    get:
      operationId: getAllAcademicSessions
      summary: List all academic sessions
      tags: [AcademicSessions]
      parameters:
        - $ref: '#/components/parameters/filter'
        - $ref: '#/components/parameters/offset'
        - $ref: '#/components/parameters/limit'
      responses:
        "200":
          description: List of academic sessions.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [academicSessions]
                properties:
                  academicSessions:
                    type: array
                    items:
                      $ref: '#/components/schemas/AcademicSession'
        "401":
          $ref: '#/components/responses/Unauthorized'

  /v1p1/academicSessions/{termId}:
    get:
      operationId: getAcademicSession
      summary: Get academic session by ID
      tags: [AcademicSessions]
      parameters:
        - $ref: '#/components/parameters/termId'
      responses:
        "200":
          description: The requested academic session.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [academicSession]
                properties:
                  academicSession:
                    $ref: '#/components/schemas/AcademicSession'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /v1p1/terms/{termId}:
    get:
      operationId: getTerm
      summary: Get term by ID (alias for academicSessions/{termId})
      description: Alias for `GET /v1p1/academicSessions/{termId}`. Handled by the same controller action.
      tags: [AcademicSessions]
      parameters:
        - $ref: '#/components/parameters/termId'
      responses:
        "200":
          description: The requested academic session.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
          content:
            application/json:
              schema:
                type: object
                required: [academicSession]
                properties:
                  academicSession:
                    $ref: '#/components/schemas/AcademicSession'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'
