openapi: 3.0.3
info:
  title: Schedula Security API
  version: 1.6.15
  description: OpenAPI document focused on security, identity, user settings, CSRF,
    and configurable CAPTCHA protection using Cloudflare Turnstile or Google reCAPTCHA.
externalDocs:
  description: Docs
  url: https://api.formulas.dev/docs/security/docs.json
servers:
- url: https://api.formulas.dev/
tags:
- name: Security
  description: Authentication, account, user settings, CSRF bootstrap, and public
    CAPTCHA configuration.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Token
    cookieAuth:
      type: apiKey
      in: cookie
      name: session
    authToken:
      type: apiKey
      in: header
      name: Authentication-Token
      description: Authentication token returned by login helpers.
  parameters:
    RequestCsrfTokenHeader:
      name: X-CSRFToken
      in: header
      required: false
      schema:
        type: string
  examples:
    UnauthorizedError:
      value:
        error: unauthorized
        message: Authentication required
    ForbiddenError:
      value:
        error: forbidden
        message: Access denied
    NotFoundError:
      value:
        error: not_found
        message: Resource not found
    ValidationError:
      value:
        error: invalid_payload
        message: Invalid payload
        details:
          field: name
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: object
          additionalProperties: true
      example:
        error: invalid_payload
        message: Invalid payload
        details:
          field: name
    Status:
      type: object
      properties:
        status:
          type: string
      example:
        status: ok
    CaptchaToken:
      type: string
      description: One-time token produced by the CAPTCHA provider selected by `GET
        /user/captcha`. Required when `CAPTCHA_PROVIDER` is `turnstile` or `recaptcha`;
        omitted when it is `none`.
      example: captcha-token-123
    CaptchaConfigResponse:
      type: object
      required:
      - provider
      - site_key
      properties:
        provider:
          type: string
          enum:
          - none
          - turnstile
          - recaptcha
          description: CAPTCHA provider currently selected by the backend.
        site_key:
          type: string
          nullable: true
          description: Public provider site key. Null when CAPTCHA is disabled.
      additionalProperties: false
      example:
        provider: turnstile
        site_key: 0x4AAAA...
    SecurityMeta:
      type: object
      properties:
        code:
          type: integer
      additionalProperties: true
      example:
        code: 200
    SecurityUserPayload:
      type: object
      properties:
        id:
          type: integer
        email:
          type: string
        username:
          type: string
          nullable: true
        firstname:
          type: string
          nullable: true
        lastname:
          type: string
          nullable: true
        avatar:
          $ref: '#/components/schemas/SecurityAvatar'
        settings:
          $ref: '#/components/schemas/SecurityJsonObject'
        custom_data:
          $ref: '#/components/schemas/SecurityJsonObject'
        roles:
          type: array
          items:
            type: string
        token:
          type: string
      additionalProperties: false
      example:
        id: 42
        email: user@example.com
        username: user
        firstname: First
        lastname: Last
        avatar: data:image/png;base64,AAAA
        roles:
        - system:admin
        token: token-123
    SecurityAvatar:
      type: string
      nullable: true
      description: Signed URL used to download the user avatar.
      example: /user/avatar/signed-token
    SecurityAvatarInput:
      type: string
      nullable: true
      description: Base64 encoded image data URL used when uploading an avatar.
      pattern: ^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$
      example: data:image/png;base64,AAAA
    SecurityJsonObject:
      type: object
      nullable: true
      additionalProperties: true
    SecurityUserSettings:
      type: object
      description: User preferences stored under `user.settings`.
      properties:
        theme:
          type: string
          description: UI theme name.
        language:
          type: string
          description: Preferred language/locale code used by localization and notifications.
          example: it
        notifications:
          type: object
          description: Notification preferences stored under `settings.notifications`.
          properties:
            channels:
              type: object
              description: Per-channel enable flags used by the notification delivery
                layer. Missing keys default to enabled.
              properties:
                in_app:
                  type: boolean
                  description: Enable in-app inbox notifications.
                  default: true
                email:
                  type: boolean
                  description: Enable email notifications.
                  default: true
                sms:
                  type: boolean
                  description: Enable SMS notifications.
                  default: true
                push:
                  type: boolean
                  description: Enable push notifications.
                  default: true
                whatsapp:
                  type: boolean
                  description: Enable WhatsApp notifications.
                  default: true
                telegram:
                  type: boolean
                  description: Enable Telegram notifications.
                  default: true
                socket:
                  type: boolean
                  description: Enable Socket.IO notifications.
                  default: true
              additionalProperties: true
          additionalProperties: true
      additionalProperties: true
      example:
        theme: light
        language: en
        notifications:
          channels:
            in_app: true
            email: true
            sms: false
            push: false
            whatsapp: false
            telegram: false
            socket: true
    SecurityUserRegisterRequest:
      type: object
      required:
      - email
      - password
      - password_confirm
      - firstname
      - lastname
      properties:
        email:
          type: string
        username:
          type: string
        password:
          type: string
        password_confirm:
          type: string
        firstname:
          type: string
        lastname:
          type: string
        avatar:
          $ref: '#/components/schemas/SecurityAvatarInput'
        custom_data:
          $ref: '#/components/schemas/SecurityJsonObject'
        captcha_token:
          $ref: '#/components/schemas/CaptchaToken'
      additionalProperties: true
      example:
        email: new.user@example.com
        password: Password123!
        password_confirm: Password123!
        firstname: First
        lastname: Last
        avatar: data:image/png;base64,AAAA
        custom_data:
          bio: runner
        captcha_token: captcha-token-123
    SecurityUserEditRequest:
      type: object
      required:
      - firstname
      - lastname
      properties:
        firstname:
          type: string
        lastname:
          type: string
        avatar:
          $ref: '#/components/schemas/SecurityAvatarInput'
        custom_data:
          $ref: '#/components/schemas/SecurityJsonObject'
      additionalProperties: true
      example:
        firstname: First
        lastname: Last
        avatar: /user/avatar/signed-token
        custom_data:
          bio: updated profile
    SecurityChangePasswordResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/SecurityMeta'
        response:
          type: object
          properties:
            user:
              $ref: '#/components/schemas/SecurityUserPayload'
            csrf_token:
              type: string
          additionalProperties: true
      additionalProperties: false
      example:
        meta:
          code: 200
        response:
          user:
            id: 1
            email: user@example.com
            username: user
            firstname: First
            lastname: Last
            avatar: /user/avatar/signed-token
            settings:
              language: en
              notifications:
                channels:
                  push: true
                  email: true
            roles:
            - admin
            token: token-123
          csrf_token: csrf-token-123
    UserSettingsResponse:
      type: object
      properties:
        settings:
          $ref: '#/components/schemas/SecurityUserSettings'
        user:
          $ref: '#/components/schemas/SecurityUserPayload'
      additionalProperties: false
      example:
        settings:
          theme: light
          language: en
          notifications:
            channels:
              in_app: true
              email: true
              sms: false
              push: false
              whatsapp: false
              telegram: false
              socket: true
        user:
          id: 42
          email: admin@example.com
          token: token-123
security:
- authToken: []
- bearerAuth: []
- cookieAuth: []
paths:
  /user/captcha:
    get:
      operationId: get_user_captcha
      tags:
      - Security
      summary: Get CAPTCHA public configuration
      description: Return the active CAPTCHA provider and its public site key. Browser
        clients use this before rendering Turnstile or reCAPTCHA. No secret key is
        exposed.
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CaptchaConfigResponse'
  /user/login:
    post:
      operationId: post_user_login
      tags:
      - Security
      summary: Login
      security: []
      parameters:
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - password
              properties:
                email:
                  type: string
                password:
                  type: string
                remember:
                  type: boolean
                captcha_token:
                  $ref: '#/components/schemas/CaptchaToken'
            example:
              email: admin@example.com
              password: AdminPass123!
              remember: true
              captcha_token: captcha-token-123
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityUserPayload'
              example:
                id: 42
                email: user@example.com
                firstname: First
                lastname: Last
                token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Authenticate a user. `captcha_token` is required when CAPTCHA is
        enabled; generate it using action `login`.
  /user/logout:
    post:
      operationId: post_user_logout
      tags:
      - Security
      summary: Logout
      security:
      - authToken: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/register:
    post:
      operationId: post_user_register
      tags:
      - Security
      summary: Register
      security: []
      parameters:
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserRegisterRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Create a user account. `captcha_token` is required when CAPTCHA
        is enabled; generate it using action `register`.
  /user/confirm/{token}:
    get:
      operationId: get_user_confirm_by_token
      tags:
      - Security
      summary: Confirm email
      security: []
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/confirm:
    post:
      operationId: post_user_confirm
      tags:
      - Security
      summary: Re-send confirmation
      security: []
      parameters:
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                captcha_token:
                  $ref: '#/components/schemas/CaptchaToken'
              additionalProperties: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Re-send the confirmation email. `captcha_token` is required when
        CAPTCHA is enabled; generate it using action `email_confirmation`.
  /user/reset:
    post:
      operationId: post_user_reset
      tags:
      - Security
      summary: Request password reset
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                captcha_token:
                  $ref: '#/components/schemas/CaptchaToken'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: Request a password-reset email. `captcha_token` is required when
        CAPTCHA is enabled; generate it using action `password_reset_request`.
  /user/reset/{token}:
    post:
      operationId: post_user_reset_by_token
      tags:
      - Security
      summary: Perform password reset
      security: []
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - password
              - password_confirm
              properties:
                password:
                  type: string
                password_confirm:
                  type: string
            example:
              password: NewPass123!
              password_confirm: NewPass123!
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityChangePasswordResponse'
              example:
                meta:
                  code: 200
                response:
                  user:
                    id: 1
                    email: vinci1it2000@gmail.com
                    username: admin
                    firstname: Vincenzo
                    lastname: Arcidiacono
                    settings:
                      canDrive: true
                      language: it
                      notifyEmail: true
                      vehicleSize: medium
                      vehicleType: diesel
                      notifications:
                        channels:
                          push: true
                          email: true
                      notifyMatches: true
                      canBePassenger: true
                      isOrganization: true
                      notifyMessages: true
                      hasCompletedOnboarding: true
                    roles:
                    - admin
                    token: .eJwNysEKhSAQBdB_uWt5pKbO-DMx4QwILSIpgujfX7uzOA8uPVCR4HD29klZQySzdbU8cwvcWiQKaRJPKrl80cayi6D6QjMRp5x_PnCMxUHvHXVy6GORjmqyDX3_ex8b7g.amNA3g.73qfz2m4jAeta-zuAlwxKKYFLmw
                  csrf_token: ImZkYTZmYmIzZTI3M2EyZGUwNDFmZmIzMjQyOGE3MDc5ZGQyZGI3MzMi.amNA3g.ZHXmTcjqw698rNM83vSW3tZnqNQ
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/change:
    post:
      operationId: post_user_change
      tags:
      - Security
      summary: Change password
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - password
              - new_password
              - new_password_confirm
              properties:
                password:
                  type: string
                new_password:
                  type: string
                new_password_confirm:
                  type: string
            example:
              password: OldPass123!
              new_password: NewPass123!
              new_password_confirm: NewPass123!
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/verify:
    get:
      operationId: get_user_verify
      tags:
      - Security
      summary: Verify account
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/edit:
    post:
      operationId: post_user_edit
      tags:
      - Security
      summary: Edit current profile
      parameters:
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserEditRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityUserPayload'
              example:
                id: 42
                email: admin@example.com
                firstname: Admin
                lastname: User
                token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      operationId: patch_user_edit
      tags:
      - Security
      summary: Patch current profile
      parameters:
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserEditRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityUserPayload'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/settings:
    get:
      operationId: get_user_settings
      tags:
      - Security
      summary: Get user settings
      parameters:
      - name: include
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettingsResponse'
      example:
        settings:
          theme: dark
          language: it
          notifications:
            email: true
            push: false
        user:
          id: 42
          email: admin@example.com
          token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: post_user_settings
      tags:
      - Security
      summary: Merge settings
      parameters:
      - name: include
        in: query
        required: false
        schema:
          type: string
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserSettings'
            example:
              theme: dark
              language: it
              notifications:
                channels:
                  in_app: true
                  email: true
                  sms: false
                  push: false
                  whatsapp: false
                  telegram: false
                  socket: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettingsResponse'
              example:
                settings:
                  theme: dark
                  language: it
                  notifications:
                    channels:
                      in_app: true
                      email: true
                      sms: false
                      push: false
                      whatsapp: false
                      telegram: false
                      socket: true
                user:
                  id: 42
                  email: admin@example.com
                  token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      operationId: patch_user_settings
      tags:
      - Security
      summary: Merge settings partially
      parameters:
      - name: include
        in: query
        required: false
        schema:
          type: string
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserSettings'
            example:
              theme: dark
              notifications:
                channels:
                  in_app: true
                  email: true
                  sms: false
                  push: false
                  whatsapp: false
                  telegram: false
                  socket: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettingsResponse'
              example:
                settings:
                  theme: dark
                  language: it
                  notifications:
                    channels:
                      in_app: true
                      email: true
                      sms: false
                      push: false
                      whatsapp: false
                      telegram: false
                      socket: true
                user:
                  id: 42
                  email: admin@example.com
                  token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      operationId: put_user_settings
      tags:
      - Security
      summary: Replace settings
      parameters:
      - name: include
        in: query
        required: false
        schema:
          type: string
      - $ref: '#/components/parameters/RequestCsrfTokenHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityUserSettings'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettingsResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/me:
    get:
      operationId: get_user_me
      tags:
      - Security
      summary: Get current user profile
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityUserPayload'
              example:
                id: 42
                email: admin@example.com
                firstname: Admin
                lastname: User
                token: token-123
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /user/avatar/{token}:
    get:
      operationId: get_user_avatar
      tags:
      - Security
      summary: Download avatar via signed URL
      security: []
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Avatar image
          content:
            image/*:
              schema:
                type: string
                format: binary
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
