openapi: '3.0.1'
info:
  version: "v1.0.1"
  title: Self-Hosted Adaptive API
  description: |
    This specification defines how to implement a self-hosted adaptive
    algorithm, so that you can define your own rules for selecting which Items
    to serve during an adaptive test via Items API.

    Your self-hosted adaptive logic will need to be encapsulated in a web
    service secured by OAuth 2 and hosted at a public URL you define. Your
    service will need to implement a number of endpoints which Learnosity will
    call during the course of your self-hosted adaptive tests.

    For example, if your service is hosted with a base URL of
    `https://adaptive.example.com/v1/adaptive`, you would need to pass the
    following in your initialization options to Items API.

    ```
    {
      "security": ...,
      "request": {
        /* Usual Items API init options */
        "user_id": ...,
        "activity_id": ...,
        "session_id": ...,
        ...

        "adaptive": {
          "type": "selfhosted",
          "selfhosted_version_major": 1,
          "engine_url": "https://adaptive.example.com/v1/adaptive",

          "custom_config": {
            /* Additional configuration for your algorithm here */
            ...
          },

          /* Additional Learnosity adaptive config here */
          ...
        }
        ...
      }
    }
    ```

    For production implementations, Learnosity Support can also configure the
    `engine_url` in your Consumer Options, so it doesn't have to be publicly exposed.

    Your service would need to support the following calls:

     * `https://adaptive.example.com/v1/adaptive/oauth2/token`: an endpoint
       supporting OAuth 2 with the [client credentials grant
       flow](https://tools.ietf.org/html/rfc6749#section-4.4) for the
       `selfhosted-adaptive` scope. (By default, Learnosity will assume the OAuth
       2 URL is `<engine_url>/oauth2/token`, but you can override the path by
       providing the fully qualified URL to the `adaptive.token_url` parameter).

     * `https://adaptive.example.com/v1/adaptive/getNextItems`: an endpoint
       which Learnosity will call before and during the adaptive test. The
       endpoint should accept session details including the scores received for
       all previous Items seen in the student's test. The response payload
       contains a list of one or more Items that should be shown to the student
       next. See the detailed interface for [`/getNextItems`](#GetNextItems)
       defined below.

    Optionally, your service can also operate on some persisted state that is
    passed to and from Learnosity in each subsequent call to `/getNextItems`.
    The state data can be mutated and returned in your response payload using
    the `custom_state` attribute, and Learnosity will pass this updated state
    data back on the next call to `/getNextItems`.

    ## Note on semantic versioning

    This API's versioning follows the [Semantic Versioning 2.0.0 scheme](https://semver.org/spec/v2.0.0.html): `v<Major>.<minor>.<patch>`. Generally speaking, this means that
      * `patch` is incremented on non-functional changes and bugfixes to this API (e.g., documentation or new optional request attributes);
      * `minor` is incremented when new features are added (e.g., new endpoints or mandatory request attributes);
      * `Major` is only incremented on breaking changes such as changes of request structure, removal of endpoints or attributes.

     Most importantly, this means that
      * all versions with identical `Major` and `minor` MUST accept and process the documented payload successfully;
      * any version with identical `Major` and larger-or-equal `minor` MUST be able to accept and process the documented payload of all smaller `minor` successfully;
      * no expectation is set as to the processing of payloads from different `Major`s.

      It is recommended that an implementation of this API checks that it can support the version specified in the requests (i.e., same `Major`, larger-or-equal `minor` than the request, any `patch`).
paths:
  /getNextItems:
    post:
      summary:
        Learnosity will call this endpoint on your self-hosted adaptive service to get the next Item(s) for a session.
        This same endpoint is also called when the adaptive session is first initialized, to retrieve the first Item(s). In this case, the `current_items` array will be present and empty.
      security:
        - OAuth2:
          - selfhosted-adaptive
      requestBody:
        description: |
          Details of the session for which the new Items are requested.
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionState'
      responses:
        '200':
          description: A response payload containing a list of one or more Items that should be shown to the student next.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NextItems'

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      description: |
        This API uses OAuth 2 with the client credentials grant flow. [More info](https://tools.ietf.org/html/rfc6749#section-4.4).
        ```
        POST /oauth2/token HTTP/1.1
        Host: server.example.com
        Content-Type: application/x-www-form-urlencoded

        grant_type=client_credentials&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw&scope=selfhosted-adaptive
        ```
        The `access_token` is returned in a JSON object.
        ```json
        HTTP/1.1 200 OK
        Content-Type: application/json;charset=UTF-8

        {
         "access_token":"2YotnFZFEjr1zCsicMWpAA",
         "token_type":"bearer",
         "expires_in":3600,
         "scope":"selfhosted-adaptive"
        }
        ```
        The time-limited token obtained from the authentication endpoint will be presented to the Adaptive endpoint as a [Bearer token](https://tools.ietf.org/html/rfc6749#section-7.1) in the HTTP headers of subsequent requests.
        ```
        GET /getNextItems HTTP/1.1
        Host: example.com
        Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
        ```
      flows:
        clientCredentials:
          tokenUrl: /oauth2/token
          scopes:
            selfhosted-adaptive: Methods for Adaptive assessments
  schemas:
    SessionState:
      required:
        - api_version
        - session_id
        - activity_id
        - user_id
        - adaptive_activity
        - current_items
      properties:
        api_version:
          $ref: '#/components/schemas/ApiVersion'
        request_uuid:
          $ref: '#/components/schemas/RequestUUID'
        session_id:
          $ref: '#/components/schemas/SessionId'
        activity_id:
          $ref: '#/components/schemas/ActivityId'
        user_id:
          $ref: '#/components/schemas/UserId'
        adaptive_activity:
          $ref: '#/components/schemas/AdaptiveActivity'
        current_items:
          type: array
          description: |
            All the Items already seen in the session. At the beginning of a session, the array should be present and empty.

            *Note: It is possible for Items to be removed from a session if a user resumes from an earlier save point.*
          items:
            $ref: '#/components/schemas/ScoredItem'
        custom_state:
          type: object
          description: |
            Container for optional custom state, persisted by Learnosity.
            The value provided here is the `custom_state` data currently persisted for this session. Set via the [`custom_state` param](#GetNextItems_response_custom_state) of the most recent getNextItems response.
    AdaptiveActivity:
      type: object
      description: |
        The [`adaptive`
        object](https://reference.learnosity.com/items-api/adaptive) from the
        Items API `request` object or Item Bank Activity Template, if
        applicable. This data is static for a given adaptive session, and will
        always contain the same data each time that session calls out to your
        adaptive backend.
      required:
        - type
        - selfhosted_version_major
      properties:
        type:
          type: string
          description: The type of Adaptive engine to use (used by Items API). In this instance, only `selfhosted` is allowed.
          example: selfhosted
        selfhosted_version_major:
          type: int
          description: The major version of the self-hosted Adaptive API supported by the Adaptive backend at `engine_url`.
          example: 1
        engine_url:
          type: string
          format: url
          description: |
            The base URL (no trailing slash) of the endpoint implementing this
            API. This instructs Items API about where to send the getNextItems
            requests.

            If unspecified, Items API will use the preconfigured URL for the
            Learnosity API Consumer.
          example: "https://adaptive.example.com/v1/adaptive"
        token_url:
          type: string
          format: url
          description: |
            The URL of the endpoint offering an OAuth2 token from pre-shared client credentials.

            If unspecified, Items API will use `/oauth2/token` on the base URL of the engine.
          example: "https://authentication.example.com/oauth/adaptive/getToken"
        custom_config:
          type: object
          description: |
            Configuration specific to your Adaptive backend. This should contain any configuration required by your algorithm to deliver the session.

            *Opaque to Learnosity*
          example:
            item_pool_id: MATH-2018
        required_tags:
          type: array
          items:
            $ref: '#/components/schemas/TagV2'
          example:
          - type: Grade
            name: "6"
          - type: Domain
            name: Domain3
          - type: Domain
            name: Domain4
          description: |
            Only Items matching these tags from the Item Bank will be considered for presentation to the user.

            *Note: This is also used to select Items for automatic inclusion in Item pools alongside the Activity*
    NextItems:
      required:
        - session_id
        - next_items
      properties:
        session_id:
          $ref: '#/components/schemas/SessionId'
        next_items:
          description: The next Items to be added to the session.
          type: array
          items:
            $ref: '#/components/schemas/Item'
        is_last_items:
          type: boolean
          description: Instructs Items API to terminate the session after these Items are completed. Default to false.
        custom_state:
          type: object
          description: |
            Container for optional custom state, persisted by Learnosity.
            The provided value will overwrite the `custom_state` data currently persisted for this session. This data will be sent in the next request to [/getNextItems](#GetNextItems) via the `custom_state` parameter.

            *Opaque to Learnosity*
    SessionId:
      type: string
      format: uuid
      description: The session ID, used to uniquely identify a user session and group subsequent requests within the same assessment.
      example: 'E439F6D7-15E4-4EBF-912F-EAA7CDB8171B'
    Item:
      required:
        - reference
      properties:
        reference:
          $ref: '#/components/schemas/ItemReference'
    ScoredItem:
      required:
        - reference
        - score
        - max_score
      properties:
        reference:
          $ref: '#/components/schemas/ItemReference'
        organisation_id:
          $ref: '#/components/schemas/OrganisationId'
        item_pool_id:
          $ref: '#/components/schemas/ItemPoolId'
        score:
          type: number
          format: float
          example: 0.5
        max_score:
          type: number
          format: float
          example: 1
    ApiVersion:
      type: string
      format: uuid
      description: |
        The version number of this API. Used to identify the format of the request payload, and that expected in the response.

        *Only valid, and required, at the root level of the request*
      example:
        $ref: '#/info/version'
    RequestUUID:
      type: string
      format: uuid
      description: |
        A unique identifier for the client's request, for tracing purposes. Useful for debugging and other investigations, as the identifier is shown to the client on error.

        *Only valid at the root level of the request*
      example: 'D32158D4-EB10-4D75-AC72-BA7292383D49'
    ActivityId:
      type: string
      format: uuid
      description: The Activity ID, used to group Sessions which are taking the same Assessment.
      example: 'B44D81BE-BCE9-48B1-8A62-89E7BF1C772F'
    UserId:
      type: string
      format: uuid
      description: The User ID, used to uniquely identify a given user across Sessions.
      example: 'CCDD021C-6F96-41D0-96EB-406D564B0DD3'
    ItemReference:
      type: string
      example: 'item-abc-123'
    OrganisationId:
      type: integer
      description: The organisation ID of the Item Bank
      example: 1
    ItemPoolId:
      type: string
      description: The reference of the Item Pool
      example: 'MATH-2018'
    TagV2:
      type: object
      description: |
        An object representing a tag with a `name` and `type`.
      properties:
        type:
          type: string
          minLength: 1
          maxLength: 255
          description: The tag type
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: The tag name
      required:
        - type
        - name
      example:
        type: Subject
        name: Maths
