openapi: 3.0.3
info:
  title: Malphite API
  version: v1
  description: |
    OpenAPI specification for the current `/api/v1` endpoints.
    Authentication uses Bearer tokens in the format `<selector>:<token>`.
servers:
  - url: /api/v1
security:
  - BearerAuth: []
tags:
  - name: auth
    description: Public authentication endpoints (no Bearer token required)
  - name: me
  - name: tokens
  - name: users
  - name: tenants
  - name: departments
  - name: roles
paths:
  /auth/login:
    post:
      tags: [auth]
      summary: Login with email and password
      description: |
        Authenticates a user with email and password and returns a Bearer token.
        This endpoint is **public** — no Authorization header required.

        The returned token can be used immediately as `Authorization: Bearer <token>`
        for all subsequent API requests.

        Rate limits (stricter than general API limits):
        - 10 failed attempts per IP per 10 minutes
        - 5 failed attempts per email+IP per 15 minutes
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            example:
              email: user@example.com
              password: "..."
              token_name: "My App"
      responses:
        '201':
          description: Login successful — token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_credentials:
                  value:
                    error: invalid_credentials
                account_inactive:
                  value:
                    error: account_inactive
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /me:
    get:
      tags: [me]
      summary: Get authenticated user profile
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  /me/tokens:
    get:
      tags: [tokens]
      summary: List current user API tokens
      parameters:
        - $ref: '#/components/parameters/Limit50'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [tokens]
      summary: Create API token for current user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenCreateRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenCreateResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /me/tokens/show/{uuid}:
    parameters:
      - $ref: '#/components/parameters/UuidPath'
    get:
      tags: [tokens]
      summary: Get current user token by UUID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenShowResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags: [tokens]
      summary: Revoke current user token by UUID
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/ServerError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /me/tokens/rotate:
    post:
      tags: [tokens]
      summary: Rotate currently used API token
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                expires_at:
                  type: string
                  format: date-time
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenCreateResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /me/tokens/revoke-all:
    post:
      tags: [tokens]
      summary: Revoke all current user API tokens
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RevokeAllResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'

  /users:
    get:
      tags: [users]
      summary: List users
      parameters:
        - $ref: '#/components/parameters/Limit25'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/OrderUsers'
        - $ref: '#/components/parameters/Dir'
        - $ref: '#/components/parameters/ActiveFilter'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [users]
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateRequest'
      responses:
        '201':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /users/show/{uuid}:
    parameters:
      - $ref: '#/components/parameters/UuidPath'
    get:
      tags: [users]
      summary: Get user by UUID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserShowResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    put:
      tags: [users]
      summary: Update user by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdateRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    patch:
      tags: [users]
      summary: Patch user by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdateRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags: [users]
      summary: Delete user by UUID
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /tenants:
    get:
      tags: [tenants]
      summary: List tenants
      parameters:
        - $ref: '#/components/parameters/Limit25'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/OrderTenants'
        - $ref: '#/components/parameters/Dir'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [tenants]
      summary: Create tenant
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantWriteRequest'
      responses:
        '201':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /tenants/show/{uuid}:
    parameters:
      - $ref: '#/components/parameters/UuidPath'
    get:
      tags: [tenants]
      summary: Get tenant by UUID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantShowResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    put:
      tags: [tenants]
      summary: Update tenant by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    patch:
      tags: [tenants]
      summary: Patch tenant by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags: [tenants]
      summary: Delete tenant by UUID
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /departments:
    get:
      tags: [departments]
      summary: List departments
      parameters:
        - $ref: '#/components/parameters/Limit25'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/OrderDepartments'
        - $ref: '#/components/parameters/Dir'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepartmentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [departments]
      summary: Create department
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentWriteRequest'
      responses:
        '201':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /departments/show/{uuid}:
    parameters:
      - $ref: '#/components/parameters/UuidPath'
    get:
      tags: [departments]
      summary: Get department by UUID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepartmentShowResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    put:
      tags: [departments]
      summary: Update department by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    patch:
      tags: [departments]
      summary: Patch department by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags: [departments]
      summary: Delete department by UUID
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /roles:
    get:
      tags: [roles]
      summary: List roles
      parameters:
        - $ref: '#/components/parameters/Limit25'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/OrderRoles'
        - $ref: '#/components/parameters/Dir'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [roles]
      summary: Create role
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleWriteRequest'
      responses:
        '201':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'

  /roles/show/{uuid}:
    parameters:
      - $ref: '#/components/parameters/UuidPath'
    get:
      tags: [roles]
      summary: Get role by UUID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleShowResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    put:
      tags: [roles]
      summary: Update role by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    patch:
      tags: [roles]
      summary: Patch role by UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleWriteRequest'
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags: [roles]
      summary: Delete role by UUID
      responses:
        '200':
          $ref: '#/components/responses/DataObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: selector:token
  parameters:
    UuidPath:
      name: uuid
      in: path
      required: true
      schema:
        type: string
        format: uuid
    Limit25:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    Limit50:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    Offset:
      name: offset
      in: query
      schema:
        type: integer
        minimum: 0
        default: 0
    Search:
      name: search
      in: query
      schema:
        type: string
    Dir:
      name: dir
      in: query
      schema:
        type: string
        enum: [asc, desc]
        default: asc
    ActiveFilter:
      name: active
      in: query
      schema:
        type: string
        description: "Accepted aliases: 1, 0, true, false, active, inactive"
    OrderUsers:
      name: order
      in: query
      schema:
        type: string
        enum: [id, uuid, first_name, last_name, display_name, email, created, modified, active, last_login_at]
        default: last_name
    OrderTenants:
      name: order
      in: query
      schema:
        type: string
        enum: [id, uuid, description, created, modified]
        default: description
    OrderDepartments:
      name: order
      in: query
      schema:
        type: string
        enum: [id, uuid, description, code, cost_center, active, created, modified]
        default: description
    OrderRoles:
      name: order
      in: query
      schema:
        type: string
        enum: [id, uuid, description, code, active, created, modified]
        default: description
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: unauthorized
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: not_found
    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: validation_error
            errors:
              field:
                - invalid_value
    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limited
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: rate_limit_exceeded
    ServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    DataObject:
      description: Generic service response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GenericDataResponse'
  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
    GenericDataResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          additionalProperties: true
    LoginRequest:
      type: object
      additionalProperties: false
      required: [email, password]
      properties:
        email:
          type: string
          format: email
          description: User email address (case-insensitive)
        password:
          type: string
          description: User password
        token_name:
          type: string
          description: Optional label for the issued token (e.g. "iPhone App"). Defaults to "Login".
          default: Login
        tenant_uuid:
          type: string
          format: uuid
          description: Optional — scope the issued token to a specific tenant UUID. Must be assigned to the user.
    LoginResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [token, token_uuid, expires_at]
          properties:
            token:
              type: string
              description: Full Bearer token in format `selector:plaintext`. Shown only once — store securely.
              example: "abc123def:456789..."
            token_uuid:
              type: string
              format: uuid
              description: UUID of the created token record (use for revocation via /me/tokens)
            expires_at:
              type: string
              format: date-time
              nullable: true
              description: UTC expiry timestamp of the token
    RoleWriteRequest:
      type: object
      additionalProperties: false
      required: [description]
      properties:
        description:
          type: string
          minLength: 1
        code:
          type: string
        active:
          type: string
          description: "Accepted aliases: 1, 0, true, false, active, inactive"
    DepartmentWriteRequest:
      type: object
      additionalProperties: false
      required: [description, tenant_id]
      properties:
        description:
          type: string
          minLength: 1
        tenant_id:
          type: integer
          minimum: 1
        code:
          type: string
        cost_center:
          type: string
        active:
          type: string
          description: "Accepted aliases: 1, 0, true, false, active, inactive"
    TenantWriteRequest:
      type: object
      additionalProperties: false
      required: [description, status]
      properties:
        description:
          type: string
          minLength: 1
        status:
          type: string
          enum: [active, inactive]
        address:
          type: string
        postal_code:
          type: string
        city:
          type: string
        country:
          type: string
        region:
          type: string
        vat_id:
          type: string
        tax_number:
          type: string
        phone:
          type: string
        fax:
          type: string
        email:
          type: string
          format: email
        support_email:
          type: string
          format: email
        support_phone:
          type: string
        billing_email:
          type: string
          format: email
        website:
          type: string
        privacy_url:
          type: string
        imprint_url:
          type: string
        primary_color:
          type: string
          description: "Hex color, e.g. #0f766e"
        primary_color_use_default:
          type: boolean
        default_theme:
          type: string
        allow_user_theme_mode:
          type: string
          enum: ['', '0', '1']
    UserCreateRequest:
      type: object
      additionalProperties: false
      required: [first_name, last_name, email, password, password2]
      properties:
        first_name:
          type: string
          minLength: 1
        last_name:
          type: string
          minLength: 1
        email:
          type: string
          format: email
        profile_description:
          type: string
        job_title:
          type: string
        phone:
          type: string
        mobile:
          type: string
        short_dial:
          type: string
        address:
          type: string
        postal_code:
          type: string
        city:
          type: string
        country:
          type: string
        region:
          type: string
        hire_date:
          type: string
          format: date
        locale:
          type: string
        theme:
          type: string
        totp_secret:
          type: string
        active:
          type: boolean
        password:
          type: string
        password2:
          type: string
        tenant_ids:
          type: array
          items:
            type: integer
            minimum: 1
        primary_tenant_id:
          type: integer
          minimum: 1
        role_ids:
          type: array
          items:
            type: integer
            minimum: 1
        department_ids:
          type: array
          items:
            type: integer
            minimum: 1
    UserUpdateRequest:
      type: object
      additionalProperties: false
      required: [first_name, last_name, email]
      properties:
        first_name:
          type: string
          minLength: 1
        last_name:
          type: string
          minLength: 1
        email:
          type: string
          format: email
        profile_description:
          type: string
        job_title:
          type: string
        phone:
          type: string
        mobile:
          type: string
        short_dial:
          type: string
        address:
          type: string
        postal_code:
          type: string
        city:
          type: string
        country:
          type: string
        region:
          type: string
        hire_date:
          type: string
          format: date
        locale:
          type: string
        theme:
          type: string
        totp_secret:
          type: string
        active:
          type: boolean
        password:
          type: string
        password2:
          type: string
        tenant_ids:
          type: array
          items:
            type: integer
            minimum: 1
        primary_tenant_id:
          type: integer
          minimum: 1
        role_ids:
          type: array
          items:
            type: integer
            minimum: 1
        department_ids:
          type: array
          items:
            type: integer
            minimum: 1
    TenantSummary:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        status:
          type: string
    AssignmentDepartment:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        active:
          type: boolean
        tenant_uuid:
          type: string
          format: uuid
          nullable: true
    AssignmentRole:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        active:
          type: boolean
    Assignments:
      type: object
      properties:
        tenants:
          type: array
          items:
            $ref: '#/components/schemas/TenantSummary'
        departments:
          type: array
          items:
            $ref: '#/components/schemas/AssignmentDepartment'
        roles:
          type: array
          items:
            $ref: '#/components/schemas/AssignmentRole'
    UserCustomFieldOption:
      type: object
      properties:
        option_key:
          type: string
        label:
          type: string
    UserCustomFieldField:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        field_key:
          type: string
        label:
          type: string
        type:
          type: string
        is_filterable:
          type: boolean
        value:
          description: Type-dependent value (string, boolean, null, or array of option keys).
        options:
          type: array
          items:
            $ref: '#/components/schemas/UserCustomFieldOption'
    UserCustomFieldTenantGroup:
      type: object
      properties:
        tenant:
          $ref: '#/components/schemas/TenantSummary'
        fields:
          type: array
          items:
            $ref: '#/components/schemas/UserCustomFieldField'
    MeResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            first_name:
              type: string
            last_name:
              type: string
            display_name:
              type: string
            email:
              type: string
            job_title:
              type: string
            phone:
              type: string
            mobile:
              type: string
            locale:
              type: string
            current_tenant:
              $ref: '#/components/schemas/TenantSummary'
            primary_tenant:
              $ref: '#/components/schemas/TenantSummary'
            can_view_permissions:
              type: boolean
            permissions:
              type: array
              items:
                type: string
            assignments:
              $ref: '#/components/schemas/Assignments'
            custom_fields:
              type: array
              items:
                $ref: '#/components/schemas/UserCustomFieldTenantGroup'
    TokenItem:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        name:
          type: string
        selector_prefix:
          type: string
        tenant_uuid:
          type: string
          format: uuid
          nullable: true
        last_used_at:
          type: string
          nullable: true
        last_ip:
          type: string
        expires_at:
          type: string
          nullable: true
        revoked_at:
          type: string
          nullable: true
        created:
          type: string
          nullable: true
        is_current_token:
          type: boolean
    TokenListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TokenItem'
        limit:
          type: integer
    TokenShowResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/TokenItem'
    TokenCreateRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        tenant_uuid:
          type: string
          format: uuid
        expires_at:
          type: string
          format: date-time
    TokenCreateResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            token_uuid:
              type: string
              format: uuid
            token:
              type: string
            selector_prefix:
              type: string
            tenant_uuid:
              type: string
              format: uuid
              nullable: true
            expires_at:
              type: string
              nullable: true
    RevokeAllResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            revoked_count:
              type: integer
            tenant_uuid:
              type: string
              format: uuid
              nullable: true
    UserListItem:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        first_name:
          type: string
        last_name:
          type: string
        display_name:
          type: string
        email:
          type: string
        job_title:
          type: string
        phone:
          type: string
        mobile:
          type: string
        active:
          type: boolean
        created:
          type: string
        tenants:
          type: array
          items:
            type: string
        departments:
          type: array
          items:
            type: string
        roles:
          type: array
          items:
            type: string
    UserListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/UserListItem'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
    UserShowResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            first_name:
              type: string
            last_name:
              type: string
            display_name:
              type: string
            email:
              type: string
            job_title:
              type: string
            phone:
              type: string
            mobile:
              type: string
            short_dial:
              type: string
            address:
              type: string
            postal_code:
              type: string
            city:
              type: string
            country:
              type: string
            active:
              type: boolean
            created:
              type: string
            modified:
              type: string
            primary_tenant:
              $ref: '#/components/schemas/TenantSummary'
            permissions:
              type: array
              items:
                type: string
            assignments:
              $ref: '#/components/schemas/Assignments'
            custom_fields:
              type: array
              items:
                $ref: '#/components/schemas/UserCustomFieldTenantGroup'
    TenantListItem:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        status:
          type: string
        city:
          type: string
        country:
          type: string
        created:
          type: string
    TenantListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TenantListItem'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
    TenantShowResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            description:
              type: string
            status:
              type: string
            address:
              type: string
            postal_code:
              type: string
            city:
              type: string
            country:
              type: string
            phone:
              type: string
            email:
              type: string
            website:
              type: string
            created:
              type: string
            modified:
              type: string
    DepartmentListItem:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        code:
          type: string
        active:
          type: boolean
        tenant_labels:
          type: array
          items:
            type: string
        created:
          type: string
    DepartmentListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/DepartmentListItem'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
    DepartmentShowResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            description:
              type: string
            code:
              type: string
            active:
              type: boolean
            tenant_id:
              type: integer
              nullable: true
            created:
              type: string
            modified:
              type: string
    RoleListItem:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        description:
          type: string
        code:
          type: string
        active:
          type: boolean
        created:
          type: string
    RoleListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RoleListItem'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
    RoleShowResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            description:
              type: string
            code:
              type: string
            active:
              type: boolean
            created:
              type: string
            modified:
              type: string
