# Users API


The complete OpenAPI specification:

```yaml
openapi: 3.0.0
info:
  version: 1.2.0
  title: Users API
  description: Endpoints for managing users 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 managing users' 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.
    BadRequest:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            code:
              $ref: '#/components/schemas/BadRequestCode'
    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
    UsersResponse:
      type: object
      required:
        - featureUsers
      properties:
        featureUsers:
          type: array
          items:
            $ref: '#/components/schemas/User'
        limit:
          type: integer
          minimum: 0
          example: 10
        offset:
          type: integer
          minimum: 0
          example: 0
        count:
          type: integer
          minimum: 0
          example: 50
          description: The total number of users for the feature site
      additionalProperties: false
    UpsertUserRequest:
      type: object
      required:
        - email
      properties:
        email:
          type: string
          format: email
          description: The email address of the user in the feature
      additionalProperties: false
  responses:
    User:
      description: Success with user details
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/User'
    Users:
      description: Success with users details
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/UsersResponse'
    NoContent:
      description: Success with no content
    BadRequest:
      description: Bad request
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/BadRequest'
    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:
    get:
      summary: List users
      description: Returns users with optional filtering and pagination.
      operationId: listFeatureUsers
      tags:
        - Users
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: email
          in: query
          description: Filter by email.
          required: false
          schema:
            type: string
            format: email
        - name: limit
          in: query
          description: Maximum number of users to return.
          required: false
          schema:
            type: integer
            minimum: 0
        - name: offset
          in: query
          description: Number of users to skip before collecting result rows.
          required: false
          schema:
            type: integer
            minimum: 0
      responses:
        200:
          $ref: '#/components/responses/Users'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
  /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'
    put:
      summary: Add or update user
      description: Creates a new user or updates an existing user if the ID already exists.
      operationId: upsertFeatureUser
      tags:
        - Users
      parameters:
        - name: featureId
          in: path
          required: true
          schema:
            type: string
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertUserRequest'
      responses:
        200:
          $ref: '#/components/responses/User'
        201:
          $ref: '#/components/responses/User'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        500:
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Remove user
      description: Deletes a user.
      operationId: deleteFeatureUser
      tags:
        - Users
      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'
```

