# Enrollment API


The complete OpenAPI specification:

```yaml
openapi: 3.0.0
info:
  version: 1.3.0
  title: Enrollment API
  description: Endpoints for managing enrollment on a site.
servers:
  - url: https://api-accept.eu.xs4face.app/api
    description: Acceptance testing server
  - url: https://api.eu.xs4face.app/api
    description: Production server
tags:
  - name: Users
    description: Operations for reading users on a site.
  - name: Consent
    description: Managing users' consent for biometric enrollment.
  - name: TOS
    description: Managing users' acceptance of the Terms of Service for biometric enrollment.
  - name: Image
    description: Managing users' image for biometric enrollment.
components:
  schemas:
    Error:
      type: object
      required:
        - title
        - detail
      properties:
        title:
          type: string
        detail:
          type: string
        errors:
          type: array
          items:
            type: object
            required: [detail, code]
            properties:
              code:
                type: string
              detail:
                type: string
      additionalProperties: false
    BadRequestCode:
      type: string
      enum: [ E000, E001, E002, E003 ]
      description: |
       * `E000` — Request properties are invalid.
       * `E001` — Request is missing required properties.
       * `E002` — Resource does not exist.
       * `E003` — Property that must be unique is already associated with a different resource.
    BadRequestImageCode:
      type: string
      enum: [ IE001, IE002, IE003, IE004, IE005, IE006, IE007, IE008, IE009, IE010, IE011 ]
      description: |
        * `IE001` — Multiple faces found
        * `IE002` — Poor resolution
        * `IE003` — Poor exposure
        * `IE004` — Poor greyscale profile
        * `IE005` — Image contains hotspots
        * `IE006` — Face not facing forward
        * `IE007` — Lighting is not uniform
        * `IE008` — Eyes are not open
        * `IE009` — Tint on glasses
        * `IE010` — Image is not sharp
        * `IE011` — Mouth is not closed
    BadRequest:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            code:
              $ref: '#/components/schemas/BadRequestCode'
    BadRequestImage:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            code:
              $ref: '#/components/schemas/BadRequestCode'
            errors:
              type: array
              items:
                type: object
                required: [detail, code]
                properties:
                  detail:
                    type: string
                  code:
                    $ref: '#/components/schemas/BadRequestImageCode'
                additionalProperties: false
    Forbidden:
      $ref: '#/components/schemas/Error'
    Unauthorized:
      $ref: '#/components/schemas/Error'
    User:
      type: object
      required:
        - userId
        - email
        - status
        - lastUpdated
      properties:
        userId:
          type: string
        email:
          type: string
          format: email
        status:
          type: string
          enum: [PENDING_TOS, PENDING_IMAGE, PENDING_CONSENT, ACTIVE]
        lastUpdated:
          type: string
          format: date-time
          description: Last updated timestamp for the user, e.g. 2026-08-04T10:57:43.332Z
      additionalProperties: false
    Consent:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum: [PENDING_TOS, PENDING_IMAGE, PENDING_CONSENT, ACTIVE]
          example: ACTIVE
      additionalProperties: false
  responses:
    User:
      description: Success with user details
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/User'
    Consent:
      description: Success with consent details
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Consent'
    NoContent:
      description: Success with no content
    BadRequest:
      description: Bad request
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/BadRequest'
    BadRequestImage:
      description: Bad request
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/BadRequestImage'
    Unauthorized:
      description: Unauthorized
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Unauthorized'
    Forbidden:
      description: Forbidden
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Forbidden'
    InternalServerError:
      description: Internal server error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
    GatewayTimeout:
      description: Timeout
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
security:
  - BearerAuth: []
paths:
  /features/{featureId}/users/{userId}:
    get:
      summary: Get user
      description: Returns a user by ID.
      operationId: getFeatureUser
      tags:
        - Users
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          $ref: '#/components/responses/User'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
  /features/{featureId}/users/{userId}/image:
    post:
      summary: Upload user image
      description: Validates and uploads an image for a user
      operationId: uploadFeatureUserImage
      tags:
        - Image
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  format: binary
                  description: The image file to upload for the user's profile.
      responses:
        204:
          $ref: '#/components/responses/NoContent'
        400:
          $ref: '#/components/responses/BadRequestImage'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
  /features/{featureId}/users/{userId}/consent:
    put:
      summary: Update user consent
      description: Provides consent for the user.
      operationId: updateFeatureUserConsent
      tags:
        - Consent
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          $ref: "#/components/responses/NoContent"
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Revoke user consent
      description: Revokes the user's consent.
      operationId: revokeFeatureUserConsent
      tags:
        - Consent
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          $ref: '#/components/responses/NoContent'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
  /features/{featureId}/users/{userId}/termsOfService:
    put:
      summary: Update terms of service consent
      description: Accepts the terms of service for the user.
      operationId: updateFeatureUserTermsOfService
      tags:
        - TOS
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          $ref: '#/components/responses/NoContent'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
```

