openapi: 3.1.0
info:
  title: 'Awaretrain API'
  description: 'Awaretrain public REST API'
  version: 1.0.0
paths:
  /public/v1/groups:
    get:
      tags:
        - group
      summary: 'List groups.'
      operationId: get_public_api_v1_group_list
      parameters:
        -
          name: search
          in: query
          description: 'Search by name'
          style: deepObject
          explode: true
          schema:
            properties:
              name:
                type: string
            type: object
        -
          name: sort
          in: query
          style: deepObject
          explode: true
          schema:
            properties:
              name:
                type: string
                enum:
                  - ASC
                  - DESC
            type: object
        -
          $ref: '#/components/parameters/pageParam'
        -
          $ref: '#/components/parameters/perPageParam'
      responses:
        '200':
          description: 'List of groups.'
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/PaginatedResponse' }
                  - { properties: { data: { type: array, items: { $ref: '#/components/schemas/GroupListResponse' } } }, type: object }
        '400':
          description: 'Bad request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
    post:
      tags:
        - group
      summary: 'Create group.'
      operationId: post_public_api_v1_grorup_create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupCreateRequest'
      responses:
        '201':
          description: 'Group created.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedResponse'
        '422':
          description: 'Invalid request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationFailedResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
  '/public/v1/groups/{group}':
    get:
      tags:
        - group
      summary: 'Details of specific group.'
      operationId: get_public_api_v1_group_details
      parameters:
        -
          name: group
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 'Group details.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupDetailsResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'Group could not be found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
    put:
      tags:
        - group
      summary: 'Update specific group.'
      operationId: put_public_api_v1_group_update
      parameters:
        -
          name: group
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroupUpdateRequest'
      responses:
        '200':
          description: 'Group updated.'
        '422':
          description: 'Invalid request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationFailedResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
    delete:
      tags:
        - group
      summary: 'Delete group.'
      operationId: delete_public_api_v1_group_delete
      parameters:
        -
          name: group
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: 'Group deleted.'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'Group could not be found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
  '/public/v1/groups/{group}/users':
    get:
      tags:
        - group
      summary: 'List user in specific group.'
      operationId: get_public_api_v1_group_user_list
      parameters:
        -
          name: search
          in: query
          description: 'Search by name, email, employeeNumber or role'
          style: deepObject
          explode: true
          schema:
            properties:
              name:
                type: string
              email:
                type: string
              employeeNumber:
                type: string
              role:
                type: string
            type: object
        -
          name: 'filter[status]'
          in: query
          description: 'Filter users by their status'
          required: false
          schema:
            $ref: '#/components/schemas/UserStatus'
        -
          name: sort
          in: query
          style: deepObject
          explode: true
          schema:
            properties:
              id:
                type: string
                enum:
                  - ASC
                  - DESC
              name:
                type: string
                enum:
                  - ASC
                  - DESC
              email:
                type: string
                enum:
                  - ASC
                  - DESC
              role:
                type: string
                enum:
                  - ASC
                  - DESC
              mfaEnabled:
                type: string
                enum:
                  - ASC
                  - DESC
              createdAt:
                type: string
                enum:
                  - ASC
                  - DESC
              deletedAt:
                type: string
                enum:
                  - ASC
                  - DESC
            type: object
        -
          $ref: '#/components/parameters/pageParam'
        -
          $ref: '#/components/parameters/perPageParam'
        -
          name: group
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 'List user in specific group.'
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/PaginatedResponse' }
                  - { properties: { data: { type: array, items: { $ref: '#/components/schemas/UserListResponse' } } }, type: object }
        '400':
          description: 'Bad request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
  /public/v1/license:
    get:
      tags:
        - license
      summary: 'License details.'
      operationId: get_public_api_v1_license_details
      responses:
        '200':
          description: 'License details.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LicenseDetailsResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'License not found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
  /public/v1/report/training/dump:
    get:
      tags:
        - report/training
      summary: 'Training report data dump.'
      operationId: get_public_api_v1_report_training_dump
      parameters:
        -
          name: filter
          in: query
          style: deepObject
          explode: true
          schema:
            properties:
              program:
                description: 'Filter by program ID'
                type: string
              module:
                description: 'Filter by module ID'
                type: string
              user:
                description: 'Filter by user ID'
                type: string
              email:
                description: 'Filter by user email'
                type: string
              group:
                description: 'Filter by group ID'
                type: string
              completed:
                type: boolean
              completedAt:
                description: 'Filter by completion date (optionally prefixed with an operator)'
                type: string
                format: date
                example: '>=2024-10-16'
              availableFrom:
                description: 'Filter by module availability date (optionally prefixed with an operator)'
                type: string
                format: date
                example: '>=2024-10-16'
            type: object
      responses:
        '200':
          description: 'Training report data dump.'
          content:
            text/csv: {  }
            application/jsonl: {  }
        '400':
          description: 'Bad request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '406':
          description: 'Not acceptable.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotAcceptableResponse'
  /public/v1/users:
    get:
      tags:
        - user
      summary: 'List users.'
      operationId: get_public_api_v1_user_list
      parameters:
        -
          name: search
          in: query
          description: 'Search by name, email, employeeNumber or role'
          style: deepObject
          explode: true
          schema:
            properties:
              name:
                type: string
              email:
                type: string
              employeeNumber:
                type: string
              role:
                type: string
            type: object
        -
          name: 'filter[status]'
          in: query
          description: 'Filter users by their status'
          required: false
          schema:
            $ref: '#/components/schemas/UserStatus'
        -
          name: sort
          in: query
          style: deepObject
          explode: true
          schema:
            properties:
              id:
                type: string
                enum:
                  - ASC
                  - DESC
              name:
                type: string
                enum:
                  - ASC
                  - DESC
              email:
                type: string
                enum:
                  - ASC
                  - DESC
              role:
                type: string
                enum:
                  - ASC
                  - DESC
              mfaEnabled:
                type: string
                enum:
                  - ASC
                  - DESC
              createdAt:
                type: string
                enum:
                  - ASC
                  - DESC
              deletedAt:
                type: string
                enum:
                  - ASC
                  - DESC
            type: object
        -
          $ref: '#/components/parameters/pageParam'
        -
          $ref: '#/components/parameters/perPageParam'
      responses:
        '200':
          description: 'List of Users.'
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/PaginatedResponse' }
                  - { properties: { data: { type: array, items: { $ref: '#/components/schemas/UserListResponse' } } }, type: object }
        '400':
          description: 'Bad request.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
    post:
      tags:
        - user
      summary: 'Create user.'
      operationId: post_public_api_v1_user_create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateRequest'
      responses:
        '201':
          description: 'User created.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '422':
          description: 'Validation error.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationFailedResponse'
  '/public/v1/users/{user}':
    get:
      tags:
        - user
      summary: 'Details of specific user.'
      operationId: get_public_api_v1_user_details
      parameters:
        -
          name: user
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 'User details.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDetailsResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'User not found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
    put:
      tags:
        - user
      summary: 'Update specific user.'
      operationId: put_public_api_v1_user_update
      parameters:
        -
          name: user
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdateRequest'
      responses:
        '200':
          description: 'Updated user details.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDetailsResponse'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '422':
          description: 'Validation error.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationFailedResponse'
    delete:
      tags:
        - user
      summary: 'Soft-delete user.'
      operationId: delete_public_api_v1_user_delete
      parameters:
        -
          name: user
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: 'User deleted.'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'User could not be found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
  '/public/v1/users/recover/{user}':
    post:
      tags:
        - user
      summary: 'Recover soft-deleted user.'
      operationId: post_public_api_v1_user_recover
      parameters:
        -
          name: user
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: 'User recovered.'
        '403':
          description: 'Access denied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessDeniedResponse'
        '404':
          description: 'User could not be found.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
components:
  schemas:
    ValidationFailedResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://api.awaretrain.com/error/be74bda6-320a-4e65-a3b9-eeea7dc27a60'
            title:
              default: 'Validation failed'
            status:
              default: 422
            detail:
              default: 'Unprocessable Content'
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
        -
          required:
            - violations
          properties:
            violations:
              type: array
              items:
                properties:
                  propertyPath: { description: 'The property path from the root value to the invalid value.', type: string }
                  title: { description: 'The violation message.', type: string }
                  template: { description: 'The raw violation message.', type: string }
                  parameters: { description: 'The parameters to substitute in the raw violaiton message.', type: object }
                type: object
          type: object
    MultiStatusResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://api.awaretrain.com/error/28706b29-e260-45c7-88ab-a498f2e61fed'
            title:
              default: 'An error occurred'
            status:
              default: 207
            detail:
              default: Multi-Status
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
        -
          properties:
            errors:
              type:
                - object
                - array
          type: object
    AuthenticationFailedResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://api.awaretrain.com/error/230ff063-2e9c-4d0d-a917-7cb5f6d2708b'
            title:
              default: 'Authentication failed.'
            status:
              default: 403
            detail:
              default: Forbidden
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
    UserStatus:
      type: string
      enum:
        - all
        - deleted
        - active
        - not_invited
        - invitation_sent
    CreatedResponse:
      required:
        - id
      properties:
        id:
          type: string
      type: object
    GroupListResponse:
      required:
        - id
        - name
      properties:
        id:
          type: string
        name:
          type: string
      type: object
    GroupDetailsResponse:
      required:
        - id
        - name
        - users
      properties:
        id:
          type: string
        name:
          type: string
        users:
          type: integer
      type: object
    UserListResponse:
      required:
        - id
        - firstname
        - lastname
        - email
        - role
        - locale
        - active
        - invitationSent
        - mfaEnabled
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        firstname:
          type: string
        middlename:
          type:
            - string
            - 'null'
        lastname:
          type: string
        email:
          type: string
        role:
          type: string
        locale:
          type: string
        employeeNumber:
          type:
            - string
            - 'null'
        active:
          type: boolean
        invitationSent:
          type: boolean
        mfaEnabled:
          type: boolean
        lastSeenAt:
          type:
            - string
            - 'null'
          format: date-time
        activatedAt:
          type:
            - string
            - 'null'
          format: date-time
        deletedAt:
          type:
            - string
            - 'null'
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      type: object
    UserListResponseGroupDetails:
      required:
        - id
        - name
      properties:
        id:
          type: string
        name:
          type: string
      type: object
    UserDetailsResponse:
      required:
        - id
        - firstname
        - lastname
        - email
        - role
        - locale
        - active
        - invitationSent
        - mfaEnabled
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        firstname:
          type: string
        middlename:
          type:
            - string
            - 'null'
        lastname:
          type: string
        email:
          type: string
        role:
          type: string
        locale:
          type: string
        employeeNumber:
          type:
            - string
            - 'null'
        active:
          type: boolean
        invitationSent:
          type: boolean
        mfaEnabled:
          type: boolean
        groups:
          type: array
          items:
            $ref: '#/components/schemas/UserDetailsResponseGroupDetails'
          default: []
        auditGroups:
          type: array
          items:
            $ref: '#/components/schemas/UserDetailsResponseGroupDetails'
          default: []
        lastSeenAt:
          type:
            - string
            - 'null'
          format: date-time
        activatedAt:
          type:
            - string
            - 'null'
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        deletedAt:
          type:
            - string
            - 'null'
          format: date-time
      type: object
    UserDetailsResponseGroupDetails:
      required:
        - id
        - name
      properties:
        id:
          type: string
        name:
          type: string
      type: object
    LicenseUsersDetails:
      properties:
        active:
          type: integer
          default: 0
        inactive:
          type: integer
          default: 0
        total:
          type: integer
          default: 0
      type: object
    LicenseOrganizationDetails:
      required:
        - name
      properties:
        name:
          type: string
        userLimit:
          type:
            - integer
            - 'null'
        endDate:
          type:
            - string
            - 'null'
          format: date-time
      type: object
    LicenseDetails:
      required:
        - name
        - phishing
        - training
        - publicApi
      properties:
        name:
          type: string
        modules:
          type: integer
          default: 0
        phishing:
          type: boolean
        training:
          type: boolean
        publicApi:
          type: boolean
      type: object
    LicenseDetailsResponse:
      required:
        - organization
        - users
      properties:
        organization:
          $ref: '#/components/schemas/LicenseOrganizationDetails'
        users:
          $ref: '#/components/schemas/LicenseUsersDetails'
        license:
          oneOf:
            -
              $ref: '#/components/schemas/LicenseDetails'
            -
              type: 'null'
      type: object
    GroupUpdateRequest:
      required:
        - name
      properties:
        name:
          type: string
      type: object
    GroupCreateRequest:
      required:
        - name
      properties:
        name:
          type: string
      type: object
    UserUpdateRequest:
      required:
        - firstname
        - middlename
        - lastname
        - email
        - role
        - locale
      properties:
        firstname:
          type: string
        middlename:
          type: string
        lastname:
          type: string
        email:
          type: string
        role:
          type: string
          enum:
            - user
            - auditor
            - 'super auditor'
            - 'user manager'
            - 'super manager'
        locale:
          type: string
        employeeNumber:
          type:
            - string
            - 'null'
        groups:
          type: array
          items:
            type: string
          default: []
      type: object
    UserCreateRequest:
      required:
        - firstname
        - middlename
        - lastname
        - email
        - role
        - locale
        - groups
      properties:
        firstname:
          type: string
        middlename:
          type: string
        lastname:
          type: string
        email:
          type: string
        role:
          type: string
          enum:
            - user
            - auditor
            - 'super auditor'
            - 'user manager'
            - 'super manager'
        locale:
          type: string
        employeeNumber:
          type:
            - string
            - 'null'
        groups:
          type: array
          items:
            type: string
      type: object
    NotAcceptableResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://tools.ietf.org/html/rfc2616#section-10'
            title:
              default: 'An error occurred'
            status:
              default: 406
            detail:
              default: 'Not Acceptable'
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
    AccessDeniedResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://tools.ietf.org/html/rfc2616#section-10'
            title:
              default: 'An error occurred'
            status:
              default: 403
            detail:
              default: Forbidden
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
    NotFoundResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://tools.ietf.org/html/rfc2616#section-10'
            title:
              default: 'An error occurred'
            status:
              default: 404
            detail:
              default: 'Not Found'
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
    BadRequestResponse:
      allOf:
        -
          properties:
            type:
              default: 'https://tools.ietf.org/html/rfc2616#section-10'
            title:
              default: 'An error occurred'
            status:
              default: 400
            detail:
              default: 'Bad Request'
          type: object
        -
          $ref: '#/components/schemas/RFC7807'
    PaginatedResponse:
      required:
        - page
        - perPage
        - pages
        - total
        - data
      properties:
        page:
          description: 'Current page number'
          type: integer
          format: int32
          minimum: 1
          example: 1
        perPage:
          description: 'Maximum number of items per page'
          type: integer
          format: int32
          minimum: 1
          example: 25
        pages:
          description: 'Total number of pages'
          type: integer
          format: int32
          minimum: 1
          example: 4
        total:
          description: 'Total number of items'
          type: integer
          format: int32
          minimum: 0
          example: 100
        extra:
          description: 'Optional extra data'
          type: object
          default: []
        data:
          description: 'Page items'
          type: array
          items:
            type: object
      type: object
      additionalProperties: false
    RFC7807:
      title: 'A problem object RFC 7807bis'
      required:
        - type
        - title
      properties:
        type:
          description: 'A URI reference RFC3986 that identifies the problem type.'
          type: string
          format: uri-reference
        title:
          description: 'A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see RFC7231, Section 3.4)'
          type: string
        status:
          description: 'The HTTP status code (RFC7231, Section 6) generated by the origin server for this occurrence of the problem.'
          type: integer
          maximum: 599
          minimum: 100
        detail:
          description: 'A human-readable explanation specific to this occurrence of the problem.'
          type: string
      type: object
      additionalProperties: true
    PaginationInfo:
      required:
        - offset
      properties:
        page:
          title: 'Page number.'
          type: integer
          default: 1
          exclusiveMinimum: 0
        perPage:
          title: 'Items per page.'
          type: integer
          default: 25
          exclusiveMinimum: 0
        maxPerPage:
          title: 'Get max items per page.'
          type:
            - integer
            - 'null'
          default: null
          exclusiveMinimum: 0
        offset:
          title: 'Offset based on the current page number and (max) items per page.'
          type: integer
      type: object
    SearchInfo:
      required:
        - globalQuery
        - fieldQuery
        - any
        - iterator
      properties:
        globalQuery:
          title: 'Get global search query.'
          description: "This returns the global search query, one that should be applied to all searchable fields. The global search\nquery can be combined with field search queries."
          type: string
        fieldQuery:
          title: 'Check if any field search query is requested.'
          description: "This only applies to search queries for individual fields, not the global search query. If this returns false it\nmay still mean there is a global search query."
          type: boolean
        any:
          title: 'Returns whether any kind of search has been requested.'
          type: boolean
        iterator:
          type: object
          additionalProperties:
            type: string
      type: object
    SortInfo:
      required:
        - any
        - iterator
      properties:
        any:
          title: 'Check if any field is requested to be sorted.'
          type: boolean
        iterator:
          title: 'Get iterator.'
          type: object
          additionalProperties:
            type: string
      type: object
    FilterInfo:
      required:
        - any
        - iterator
      properties:
        any:
          title: 'Check if any filter is requested.'
          type: boolean
        iterator:
          $ref: '#/components/schemas/Traversable'
      type: object
    Traversable:
      type: object
  parameters:
    pageParam:
      name: page
      in: query
      description: 'Page number.'
      schema:
        type: integer
        default: 1
        minimum: 1
    perPageParam:
      name: perPage
      in: query
      description: 'Number of items per page (maximum 1000).'
      schema:
        type: integer
        default: 25
        maximum: 1000
        minimum: 1
  securitySchemes:
    Bearer:
      type: http
      bearerFormat: JWT
      scheme: bearer
security:
  -
    Bearer: null
tags:
  -
    name: license
    description: 'License endpoints'
  -
    name: user
    description: 'User management endpoints'
  -
    name: group
    description: 'Group endpoints'
  -
    name: report/training
    description: 'Training report endpoints'
