openapi: 3.0.3

info:
  title: yourBureau API
  description: |
    Multi-tenant REST API for yourBureau.

    ## Authentication
    Use **either**:
    - **JWT:** `Authorization: Bearer <token>` from `POST /api/jwt` (tenant, username, password).
      The tenant database is taken from the **`tenant` claim inside the JWT** — **`X-Tenant` is not required**.
      If you do send `X-Tenant`, it must match the token's `tenant` claim.
    - **API key:** Send **`X-Tenant` and `X-API-Key` together** (both mandatory). The key is stored in the tenant database; do not use `Authorization: Bearer` when authenticating with only an API key.

    If both `Authorization: Bearer` (valid JWT) and `X-API-Key` are sent, the JWT is used and the API key is ignored.

    ## API key permission levels

    Keys use one of four permission levels: `read_restricted`, `read_full`, `read_write_restricted`, or `read_write_full`.
    They are evaluated against the same route permissions as JWT users.

    General rules that apply across levels:

    - Read paths map to `view_*` (and related read-only names).
    - `export_*` requires at least `read_full`.

    ### `read_restricted`

    - Allows **GET requests only** (no POST/PUT/PATCH/DELETE).
    - For `time-reports`, responses are limited to entries in the **current year**.

    ### `read_full`

    - Allows **GET requests only** (no POST/PUT/PATCH/DELETE).
    - Grants access to `export_*` routes.

    ### `read_write_restricted`

    - Uses the same read rules as above for GET requests.
    - Applies a **fail-closed write allowlist**: mutating methods are only possible on explicitly allowlisted resources.
    - All other resources stay **read-only**, including `articles`, `users`, and all `references` resources.
    - For `time-reports`, read and write are limited to entries in the **current year**. Updates to existing `time-reports` outside the current year are only allowed if the effective date keeps the original year.

    ### `read_write_full`

    - Allows **every defined route**.

    ## Two-Factor Authentication (2FA)
    Users can optionally enable TOTP-based two-factor authentication.
    When 2FA is enabled, the `two_factor_code` parameter must be included in the
    `POST /api/jwt` request alongside tenant, username, and password.

    ## Permissions
    Each endpoint is associated with specific permissions.
    Required permissions are documented per endpoint.

    ## Validation and write semantics (API V3)
    For `/api/v3/*` write endpoints, request payloads are validated for required
    fields, allowed ranges, type, and format according to the API contract.
    `PUT` and `PATCH` share identical partial-update semantics; `PUT` is not a
    full-replacement operation in API V3.

    ## Pagination
    List endpoints support pagination via `limit`, `offset`, and `page` parameters.
    Default limit: 100, maximum: 1000.

    ## Rate limits
    - `POST /api/jwt`: max. **5 requests per minute per IP**.
    - `api/v3/*`: max. **100 requests per minute per tenant** by default.

    ## Sorting
    List endpoints support sorting via the `order_by` parameter.
    Multiple sort criteria can be specified comma-separated (e.g. `lastname ASC,firstname DESC`).
  version: 3.0.4
  contact:
    name: yourBureau

servers:
  - url: https://api.yourbureau.ch
    description: Production environment

tags:
  - name: Authentication
    description: JWT login, token generation, and two-factor authentication
  - name: Customers
    description: Customer management
  - name: Contacts
    description: Contact management
  - name: Customer Contacts
    description: Links between customers and contacts (roles, partner flag, comments)
  - name: Projects
    description: Project management
  - name: Outgoings
    description: Outgoing expenses (Ausgaben) with distributor, customer, project, and state relations
  - name: Distributors
    description: Supplier/distributor master data for outgoings
  - name: Articles
    description: Article management
  - name: Users
    description: User management
  - name: Settings
    description: Tenant configuration settings (non-sensitive values only)
  - name: Time Reports
    description: Time tracking reports (`rapport`) with linked business relations
  - name: Time Report Expenses
    description: Expense rows per time report (`rapport_expenses`)
  - name: User Absences
    description: User absences (holiday scheduling per user)
  - name: Support Tickets
    description: Support tickets with history
  - name: Comments
    description: Comments linked to business resources
  - name: Open Tasks
    description: Open tasks with related customer, project, order, contact, and user assignments
  - name: "References: Salutations"
    description: "Reference resource: Salutations"
  - name: "References: Countries"
    description: "Reference resource: Countries"
  - name: "References: Cantons"
    description: "Reference resource: Cantons"
  - name: "References: Currencies"
    description: "Reference resource: Currencies"
  - name: "References: Departments"
    description: "Reference resource: Departments"
  - name: "References: Places"
    description: "Reference resource: Places"
  - name: "References: Encashments"
    description: "Reference resource: Encashments"
  - name: "References: Languages"
    description: "Reference resource: Languages"
  - name: "References: Absence Types"
    description: "Reference resource: Absence Types"
  - name: "References: Article Categories"
    description: "Reference resource: Article Categories"
  - name: "References: Article Taxes"
    description: "Reference resource: Article Taxes"
  - name: "References: Article Units"
    description: "Reference resource: Article Units"
  - name: "References: Bank Accounts"
    description: "Reference resource: Bank Accounts"
  - name: "References: Email Templates"
    description: "Reference resource: Email Templates"
  - name: "References: Offer States"
    description: "Reference resource: Offer States"
  - name: "References: Order Categories"
    description: "Reference resource: Order Categories"
  - name: "References: Order Scheduling Categories"
    description: "Reference resource: Order Scheduling Categories"
  - name: "References: Order States"
    description: "Reference resource: Order States"
  - name: "References: Invoice States"
    description: "Reference resource: Invoice States"
  - name: "References: Outgoing States"
    description: "Reference resource: Outgoing workflow states"
  - name: "References: Outgoing Taxes"
    description: "Reference resource: Outgoing tax rates"
  - name: "References: Customer States"
    description: "Reference resource: Customer States"
  - name: Offers
    description: Offers with articles, history, and state management
  - name: Orders
    description: Orders with articles, history, and state management
  - name: Invoices
    description: Invoices with articles, history, payment, and state management
  - name: Assistant
    description: |
      KPI/info-query catalog and document workflows (offer, order, invoice) under `/api/v3/assistant/*`.
      Uses the same JWT or API-key auth as the rest of API V3.
  - name: "References: Project States"
    description: "Reference resource: Project States"
  - name: "References: Open Task States"
    description: "Reference resource: Open Task States"
  - name: "References: Open Task Categories"
    description: "Reference resource: Open Task Categories"
  - name: "References: Expenses"
    description: "Reference resource: Expense definitions"
  - name: "References: Public Holidays"
    description: "Reference resource: Public holidays"

x-tagGroups:
  - name: API resources
    tags:
      - Authentication
      - Customers
      - Contacts
      - Customer Contacts
      - Projects
      - Outgoings
      - Distributors
      - Articles
      - Settings
      - Users
      - User Absences
      - Support Tickets
      - Comments
      - Open Tasks
      - Time Reports
      - Time Report Expenses
      - Offers
      - Orders
      - Invoices
      - Assistant
  - name: Reference data
    tags:
      - "References: Salutations"
      - "References: Countries"
      - "References: Cantons"
      - "References: Currencies"
      - "References: Departments"
      - "References: Places"
      - "References: Encashments"
      - "References: Languages"
      - "References: Absence Types"
      - "References: Article Categories"
      - "References: Article Taxes"
      - "References: Article Units"
      - "References: Bank Accounts"
      - "References: Email Templates"
      - "References: Offer States"
      - "References: Order Categories"
      - "References: Order Scheduling Categories"
      - "References: Order States"
      - "References: Invoice States"
      - "References: Outgoing States"
      - "References: Outgoing Taxes"
      - "References: Customer States"
      - "References: Project States"
      - "References: Open Task States"
      - "References: Open Task Categories"
      - "References: Expenses"
      - "References: Public Holidays"

security:
  - BearerAuth: []
  - ApiKeyAuth: []
    TenantHeader: []


# =============================================================================
# PATHS
# =============================================================================
paths:

  # ---------------------------------------------------------------------------
  # Authentication
  # ---------------------------------------------------------------------------
  /api/jwt:
    post:
      tags: [Authentication]
      summary: Generate JWT token
      description: |
        Authenticates a user and returns a JWT token.
        Rate-limited to 5 requests per minute per IP address.
        This endpoint does not require prior authentication.

        If the user has two-factor authentication enabled, the `two_factor_code` parameter
        is required. If omitted, the response will indicate that 2FA is required (403).
      operationId: jwtLogin
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Successful authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: |
            Invalid credentials, invalid tenant, or invalid two-factor code.
            When `two_factor_required` is `true`, the client must resend the request
            including the `two_factor_code` parameter.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      two_factor_required:
                        type: boolean
                        description: Indicates that a TOTP code is required to complete authentication
                        example: true
        '403':
          description: |
            Two-factor authentication is enabled and `two_factor_code` is missing.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      two_factor_required:
                        type: boolean
                        description: Indicates that a TOTP code is required to complete authentication
                        example: true
        '429':
          description: Too many login attempts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 429
                error: 429
                messages:
                  error: "Too Many Requests"

  # ---------------------------------------------------------------------------
  # Customers
  # ---------------------------------------------------------------------------
  /api/v3/customers:
    get:
      tags: [Customers]
      summary: List all customers
      description: |
        Returns a paginated list of all customers.
        Supports full-text search and custom filters.
        Each item includes `customer_registers` linked to the customer.

        **Required permission:** `view_customers` or `export_customers`.

      operationId: listCustomers
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_state_id
          in: query
          description: Filter by customer state (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_register_id
          in: query
          description: Filter by customer register (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: email
          in: query
          description: |
            Case-insensitive substring match on `email` and `email2` (OR within each term).
            Comma-separated values are combined with OR (any term may match either field).
          schema:
            type: string
            example: "user@example.org"
        - name: website
          in: query
          description: Substring match on `website` (comma-separated values OR-combined).
          schema:
            type: string
            example: "example.com"
        - name: zip
          in: query
          description: Substring match on `zip` (comma-separated values OR-combined).
          schema:
            type: string
            example: "8000"
        - name: country_id
          in: query
          description: Filter by country (comma-separated for multiple values)
          schema:
            type: string
            example: "1,41"
        - name: canton_id
          in: query
          description: Filter by canton (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: newsletter
          in: query
          description: Filter by newsletter flag (0 or 1; comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: language_id
          in: query
          description: Filter by language id (comma-separated for multiple values). Same as `language` if only one is used.
          schema:
            type: string
            example: "1"
        - name: language
          in: query
          description: Alias for `language_id` when `language_id` is omitted (comma-separated supported).
          schema:
            type: string
            example: "1"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: No permission for linked resource
          headers:
            X-Authorization-Error:
              description: Machine-readable authorization error key.
              schema:
                type: string
                example: insufficient-linked-resource-permission
            X-Linked-Resource-Type:
              description: Normalized linked resource type from the request.
              schema:
                type: string
                example: offer
            X-Linked-Resource-Id:
              description: Linked resource ID from the request.
              schema:
                type: string
                example: "5102"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                error: 403
                messages:
                  error: "Forbidden"
    post:
      tags: [Customers]
      summary: Create customer
      description: |
        **Required permission:** `add_customer`.

        At least one of `company` or `lastname` must be provided.
      operationId: createCustomer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingName:
                  summary: Neither company name nor last name provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      company: "Either company or lastname must be provided."
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: No permission for linked resource
          headers:
            X-Authorization-Error:
              description: Machine-readable authorization error key.
              schema:
                type: string
                example: insufficient-linked-resource-permission
            X-Linked-Resource-Type:
              description: Normalized linked resource type from the request.
              schema:
                type: string
                example: offer
            X-Linked-Resource-Id:
              description: Linked resource ID from the request.
              schema:
                type: string
                example: "5102"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                error: 403
                messages:
                  error: "Forbidden"
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/customers/{id}:
    get:
      tags: [Customers]
      summary: Get single customer
      description: |
        Returns one customer including embedded relations (salutation, country, canton, customer_state, language) and `customer_registers`
        (all registers linked via `customer_register_link`, same shape as in the list endpoint).

        **Required permission:** `view_customers` or `export_customers`.

      operationId: getCustomer
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: No permission for linked resource
          headers:
            X-Authorization-Error:
              description: Machine-readable authorization error key.
              schema:
                type: string
                example: insufficient-linked-resource-permission
            X-Linked-Resource-Type:
              description: Normalized linked resource type from the linked comment row.
              schema:
                type: string
                example: offer
            X-Linked-Resource-Id:
              description: Linked resource ID from the linked comment row.
              schema:
                type: string
                example: "5102"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                error: 403
                messages:
                  error: "Forbidden"
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Customers]
      summary: Update customer
      description: "Also available via PATCH. Required permission: `edit_customer`."
      operationId: updateCustomer
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be 0 or 1."
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Customers]
      summary: Patch customer
      description: "Also available via PUT. Required permission: `edit_customer`."
      operationId: patchCustomer
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be 0 or 1."
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Customers]
      summary: Delete customer
      description: "Required permission: `remove_customer`."
      operationId: deleteCustomer
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Contacts
  # ---------------------------------------------------------------------------
  /api/v3/contacts:
    get:
      tags: [Contacts]
      summary: List all contacts
      description: |
        Returns a paginated list of contacts.
        Supports full-text search and custom filters.

        **Required permission:** `view_contacts` or `export_contacts`.

      operationId: listContacts
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: salutation_id
          in: query
          description: Filter by salutation (comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: country_id
          in: query
          description: Filter by country (comma-separated for multiple values)
          schema:
            type: string
            example: "1,41"
        - name: canton_id
          in: query
          description: Filter by canton (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: language_id
          in: query
          description: Filter by language (comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: is_company
          in: query
          description: Filter by company flag (0 = person, 1 = company)
          schema:
            type: string
            example: "0,1"
        - name: contact_type_id
          in: query
          description: Filter by contact type (comma-separated for multiple values)
          schema:
            type: string
            example: "2"
        - name: user_id_owner
          in: query
          description: Filter by owner user (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: email
          in: query
          description: |
            Substring match on `email1` and `email2` (OR within each term).
            Comma-separated values are combined with OR (any term may match either field).
          schema:
            type: string
            example: "user@example.org"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Contacts]
      summary: Create contact
      description: |
        **Required permission:** `add_contact`.

        At least one of `company` or `lastname` must be provided.
      operationId: createContact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingName:
                  summary: Neither company name nor last name provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      company: "Either company or lastname must be provided."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/contacts/{id}:
    get:
      tags: [Contacts]
      summary: Get single contact
      description: "Required permission: `view_contacts` or `export_contacts`."
      operationId: getContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Contacts]
      summary: Update contact
      description: "Also available via PATCH. Required permission: `edit_contact`."
      operationId: updateContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email1: "The email1 field must contain a valid email address."
                invalidBirthday:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      birthday: "The birthday field must be a valid date."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Contacts]
      summary: Patch contact
      description: "Also available via PUT. Required permission: `edit_contact`."
      operationId: patchContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email1: "The email1 field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Contacts]
      summary: Delete contact
      description: "Required permission: `remove_contact`."
      operationId: deleteContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Customer contacts (junction)
  # ---------------------------------------------------------------------------
  /api/v3/customer-contacts:
    get:
      tags: [Customer Contacts]
      summary: List customer–contact links
      description: |
        Returns a paginated list of customer–contact assignments.
        Filter by `customer_id` and/or `contact_id` (comma-separated for multiple values).
        Filter by `contact_email` on the linked contact's `email1` / `email2`, or by `customer_email` on the linked customer's `email` / `email2` (substring, via join).
        Supports full-text search on function and comment fields.

        **Required permission:** `view_customers`.

      operationId: listCustomerContacts
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_id
          in: query
          description: Filter by customer (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: contact_id
          in: query
          description: Filter by contact (comma-separated for multiple values)
          schema:
            type: string
            example: "100,101"
        - name: contact_email
          in: query
          description: |
            Substring match on the linked contact's `email1` and `email2` (OR within each term).
            Comma-separated values are combined with OR (any term may match either field).
          schema:
            type: string
            example: "user@example.org"
        - name: customer_email
          in: query
          description: |
            Substring match on the linked customer's `email` and `email2` (OR within each term).
            Comma-separated values are combined with OR (any term may match either field).
          schema:
            type: string
            example: "kunde@example.org"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerContactListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Customer Contacts]
      summary: Create customer–contact link
      description: "Required permission: `edit_customer`."
      operationId: createCustomerContact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerContactInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      contact_id: "The contact_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/customer-contacts/{id}:
    get:
      tags: [Customer Contacts]
      summary: Get single customer–contact link
      description: "Required permission: `view_customers`."
      operationId: getCustomerContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerContact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Customer Contacts]
      summary: Update customer–contact link
      description: "Required permission: `edit_customer`."
      operationId: updateCustomerContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerContactInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      contact_id: "The contact_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Customer Contacts]
      summary: Delete customer–contact link
      description: "Required permission: `edit_customer`."
      operationId: deleteCustomerContact
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Projects
  # ---------------------------------------------------------------------------
  /api/v3/projects:
    get:
      tags: [Projects]
      summary: List all projects
      description: |
        Returns a paginated list of projects.
        Supports full-text search and custom filters.

        **Required permission:** `view_projects`.

      operationId: listProjects
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: project_state_id
          in: query
          description: Filter by project state (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_id
          in: query
          description: Filter by customer (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: user_id
          in: query
          description: Filter by assigned user (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: responsible_user_id
          in: query
          description: Filter by responsible user (comma-separated for multiple values)
          schema:
            type: string
            example: "9"
        - name: active
          in: query
          description: "Filter by active state (0 = inactive, 1 = active)"
          schema:
            type: string
            example: "1"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Projects]
      summary: Create project
      description: |
        **Required permission:** `add_project`.

        The fields `customer_id` and `project` are mandatory on create.
      operationId: createProject
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                missingProject:
                  summary: Missing project name
                  value:
                    status: 422
                    error: 422
                    messages:
                      project: "The project field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/projects/{id}:
    get:
      tags: [Projects]
      summary: Get single project
      description: "Required permission: `view_projects`."
      operationId: getProject
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Projects]
      summary: Update project
      description: "Also available via PATCH. Required permission: `edit_project`."
      operationId: updateProject
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
                invalidDate:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      date_end: "The date_end field must be a valid date."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Projects]
      summary: Patch project
      description: "Also available via PUT. Required permission: `edit_project`."
      operationId: patchProject
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Projects]
      summary: Delete project
      description: "Required permission: `remove_project`."
      operationId: deleteProject
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'


  # ---------------------------------------------------------------------------
  # Outgoings
  # ---------------------------------------------------------------------------
  /api/v3/outgoings:
    get:
      tags: [Outgoings]
      summary: List all outgoings
      description: |
        Returns a paginated list of outgoing expenses.
        Supports full-text search and custom filters.

        **Required permission:** `view_outgoings`.
      operationId: listOutgoings
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_id
          in: query
          description: Filter by customer (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: project_id
          in: query
          description: Filter by project (comma-separated for multiple values)
          schema:
            type: string
            example: "15"
        - name: distributor_id
          in: query
          description: Filter by distributor (comma-separated for multiple values)
          schema:
            type: string
            example: "7"
        - name: state_id
          in: query
          description: Filter by outgoing state (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: order_id
          in: query
          description: Filter by order (comma-separated for multiple values)
          schema:
            type: string
            example: "120"
        - name: currency_id
          in: query
          description: Filter by currency
          schema:
            type: string
            example: "1"
        - name: encashment_id
          in: query
          description: Filter by payment method
          schema:
            type: string
            example: "2"
        - name: outgoing_tax_id
          in: query
          description: Filter by outgoing tax
          schema:
            type: string
            example: "1"
        - name: place_id
          in: query
          description: Filter by place
          schema:
            type: string
            example: "1"
        - name: user_id
          in: query
          description: Filter by creator user
          schema:
            type: string
            example: "5"
        - name: user_id_owner
          in: query
          description: Filter by owner user
          schema:
            type: string
            example: "5"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Outgoings]
      summary: Create outgoing
      description: |
        **Required permission:** `add_outgoing`.

        On create, `outgoing`, `customer_id`, `distributor_id`, and `end_price` are required.
        When omitted, defaults may be applied for `date`, `date_pay`, `create_date`, `state_id`,
        `currency_id`, `outgoing_tax_id`, and `encashment_id`.
        `user_id` and `user_id_owner` are set from the authenticated user and are not writable.
      operationId: createOutgoing
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingOutgoing:
                  summary: Missing outgoing label
                  value:
                    status: 422
                    error: 422
                    messages:
                      outgoing: "The outgoing field is required."
                missingDistributor:
                  summary: Missing distributor
                  value:
                    status: 422
                    error: 422
                    messages:
                      distributor_id: "The distributor_id field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/outgoings/{id}:
    get:
      tags: [Outgoings]
      summary: Get single outgoing
      description: |
        Returns one outgoing including nested `distributor`, `customer`, `project`, `state`,
        `outgoing_tax`, `currency`, and `encashment` when resolvable.

        **Required permission:** `view_outgoings`.
      operationId: getOutgoing
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Outgoings]
      summary: Update outgoing
      description: "Also available via PATCH. Required permission: `edit_outgoing`."
      operationId: updateOutgoing
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidEndPrice:
                  summary: Invalid end_price
                  value:
                    status: 422
                    error: 422
                    messages:
                      end_price: "The end_price field must contain a decimal number."
                distributorNotFound:
                  summary: Distributor does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      distributor_id: "The provided distributor_id does not exist."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Outgoings]
      summary: Patch outgoing
      description: "Also available via PUT. Required permission: `edit_outgoing`."
      operationId: patchOutgoing
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidDate:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      date: "The date field must be a valid date."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Outgoings]
      summary: Delete outgoing
      description: "Required permission: `remove_outgoing`."
      operationId: deleteOutgoing
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'


  /api/v3/outgoings/{id}/pdf:
    get:
      tags: [Outgoings]
      summary: Get outgoing PDF archive metadata
      description: |
        Returns metadata for the immutable archived receipt PDF on an outgoing.
        Returns `404` when no PDF has been archived yet.

        **Required permission:** `view_outgoings`.
      operationId: getOutgoingPdf
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingPdf'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      tags: [Outgoings]
      summary: Archive outgoing PDF
      description: |
        Upload a receipt PDF as JSON with Base64 content. Exactly **one** PDF per
        outgoing; once archived it cannot be replaced or deleted (HTTP `409`).

        - Max raw size: **2 MB**
        - Allowed MIME type: `application/pdf`
        - Response returns metadata only (no Base64 payload)
        - File is stored under `{userdataRoot}/{tenant}/outgoing/{id}.pdf` (Master-compatible)

        **Required permission:** `edit_outgoing` or `add_outgoing`.
      operationId: createOutgoingPdf
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingPdfUploadInput'
            example:
              document_name: "beleg-buero.pdf"
              filetype: "application/pdf"
              content_base64: "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL1BhZ2U+PgplbmRvYmoK"
      responses:
        '201':
          description: Successfully archived
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '409':
          description: PDF already archived
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: integer, example: 409 }
                  error: { type: integer, example: 409 }
                  messages:
                    type: object
                    properties:
                      error:
                        type: string
                        example: "Outgoing PDF is already archived and cannot be replaced."
              examples:
                alreadyArchived:
                  summary: PDF already present
                  value:
                    status: 409
                    error: 409
                    messages:
                      error: "Outgoing PDF is already archived and cannot be replaced."
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: integer, example: 413 }
                  error: { type: integer, example: 413 }
                  messages:
                    type: object
                    properties:
                      error:
                        type: string
                        example: "File exceeds the maximum allowed size of 2 MB."
              examples:
                tooLarge:
                  summary: File larger than 2 MB
                  value:
                    status: 413
                    error: 413
                    messages:
                      error: "File exceeds the maximum allowed size of 2 MB."
        '415':
          description: Unsupported media type
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: integer, example: 415 }
                  error: { type: integer, example: 415 }
                  messages:
                    type: object
                    properties:
                      error:
                        type: string
              examples:
                unsupportedType:
                  summary: Unsupported MIME type
                  value:
                    status: 415
                    error: 415
                    messages:
                      error: "Unsupported media type. Allowed: application/pdf."
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingBase64:
                  summary: Missing Base64 content
                  value:
                    status: 422
                    error: 422
                    messages:
                      content_base64: "The content_base64 field is required."
                invalidBase64:
                  summary: Invalid Base64
                  value:
                    status: 422
                    error: 422
                    messages:
                      content_base64: "The content_base64 field must contain valid Base64 data."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/outgoings/{id}/pdf/content:
    get:
      tags: [Outgoings]
      summary: Download outgoing PDF content
      description: |
        By default returns the raw PDF as a binary download.
        Pass `encoding=base64` to receive a JSON payload with `content_base64`.
        Returns `409` when the stored file hash does not match `pdf_sha256`.

        **Required permission:** `view_outgoings`.
      operationId: getOutgoingPdfContent
      parameters:
        - $ref: '#/components/parameters/idParam'
        - name: encoding
          in: query
          description: Set to `base64` for a JSON response with Base64 content
          schema:
            type: string
            enum: [base64]
            example: base64
      responses:
        '200':
          description: File content (binary) or JSON with Base64
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingPdfContentBase64'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  # ---------------------------------------------------------------------------
  # Distributors
  # ---------------------------------------------------------------------------
  /api/v3/distributors:
    get:
      tags: [Distributors]
      summary: List all distributors
      description: |
        Returns a paginated list of distributors (suppliers).
        Supports full-text search and custom filters.

        **Required permission:** `view_distributors` or `view_distributor_details`.
      operationId: listDistributors
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: active
          in: query
          description: "Filter by active state (0 = inactive, 1 = active)"
          schema:
            type: string
            example: "1"
        - name: country_id
          in: query
          description: Filter by country
          schema:
            type: string
            example: "1"
        - name: canton_id
          in: query
          description: Filter by canton
          schema:
            type: string
            example: "1"
        - name: user_id_owner
          in: query
          description: Filter by owner user
          schema:
            type: string
            example: "5"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DistributorListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Distributors]
      summary: Create distributor
      description: |
        **Required permission:** `manage_distributor`.

        The field `company` is mandatory on create.
      operationId: createDistributor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DistributorCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCompany:
                  summary: Missing company
                  value:
                    status: 422
                    error: 422
                    messages:
                      company: "The company field is required."
                invalidEmail:
                  summary: Invalid email
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/distributors/{id}:
    get:
      tags: [Distributors]
      summary: Get single distributor
      description: |
        Returns one distributor including nested `country` and `canton` when resolvable.

        **Required permission:** `view_distributors` or `view_distributor_details`.
      operationId: getDistributor
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Distributor'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Distributors]
      summary: Update distributor
      description: "Also available via PATCH. Required permission: `manage_distributor`."
      operationId: updateDistributor
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DistributorInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Distributors]
      summary: Patch distributor
      description: "Also available via PUT. Required permission: `manage_distributor`."
      operationId: patchDistributor
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DistributorInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidNewsletter:
                  summary: Invalid newsletter value
                  value:
                    status: 422
                    error: 422
                    messages:
                      newsletter: "The newsletter field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Distributors]
      summary: Delete distributor
      description: "Required permission: `manage_distributor`."
      operationId: deleteDistributor
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Articles
  # ---------------------------------------------------------------------------
  /api/v3/articles:
    get:
      tags: [Articles]
      summary: List all articles
      description: |
        Returns a paginated list of articles.
        Supports full-text search and custom filters.

        **Required permission:** `view_articles`.

      operationId: listArticles
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: article_category_id
          in: query
          description: Filter by article category (comma-separated for multiple values)
          schema:
            type: string
            example: "3,4"
        - name: article_sub_category_id
          in: query
          description: Filter by article sub-category (comma-separated for multiple values)
          schema:
            type: string
            example: "8"
        - name: article_tax_id
          in: query
          description: Filter by article tax (comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: article_unit_id
          in: query
          description: Filter by article unit (comma-separated for multiple values)
          schema:
            type: string
            example: "2"
        - name: owner_user_id
          in: query
          description: Filter by owner user (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: active
          in: query
          description: "Filter by active state (0 = inactive, 1 = active)"
          schema:
            type: string
            example: "1"
        - name: has_stock
          in: query
          description: "Filter by stock handling (0 = no, 1 = yes)"
          schema:
            type: string
            example: "1"
        - name: rapport
          in: query
          description: "Filter by time report usage (0 = no, 1 = yes)"
          schema:
            type: string
            example: "1"
        - name: is_rapport
          in: query
          description: "Alias for `rapport` filter (0 = no, 1 = yes)"
          schema:
            type: string
            enum: ["0", "1"]
            example: "1"
        - name: article_id_external
          in: query
          description: Filter by external article ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1501"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Articles]
      summary: Create article
      description: |
        **Required permission:** `add_article`.

        The fields `article_tax_id`, `article_unit_id`, and `article` are mandatory on create.
      operationId: createArticle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingArticle:
                  summary: Missing article name
                  value:
                    status: 422
                    error: 422
                    messages:
                      article: "The article field is required."
                missingTax:
                  summary: Missing tax ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_tax_id: "The article_tax_id field is required."
                missingUnit:
                  summary: Missing unit
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_unit_id: "The article_unit_id field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/articles/{id}:
    get:
      tags: [Articles]
      summary: Get single article
      description: |
        Returns one article including selected relations.
        Includes `article_category`, `article_tax`, `article_unit` when available.

        **Required permission:** `view_articles`.

      operationId: getArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Articles]
      summary: Update article
      description: "Also available via PATCH. Required permission: `edit_article`."
      operationId: updateArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidPrice:
                  summary: Invalid article price
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_price: "The article_price field must contain a decimal number."
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Articles]
      summary: Patch article
      description: "Also available via PUT. Required permission: `edit_article`."
      operationId: patchArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidPrice:
                  summary: Invalid article price
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_price: "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Articles]
      summary: Delete article
      description: "Required permission: `delete_article`."
      operationId: deleteArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Users
  # ---------------------------------------------------------------------------
  /api/v3/my-user:
    get:
      tags: [Users]
      summary: Get own user profile (JWT only)
      description: |
        Returns the profile of the authenticated JWT user.
        No role permissions are required; a valid JWT Bearer token is sufficient.
        This endpoint requires JWT Bearer authentication; API key authentication is rejected with `403`.
      operationId: getMyUser
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyUserProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Users]
      summary: Update own user profile (JWT only)
      description: |
        Updates the profile of the authenticated JWT user.
        Also available via PATCH. No role permissions are required.
        Only `firstname`, `lastname`, `username`, `email`, `language`, and `password` may be changed.
        Admin fields such as `active` or feature flags are ignored if sent.
        This endpoint requires JWT Bearer authentication; API key authentication is rejected with `403`.
      operationId: updateMyUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MyUserProfileInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyUserProfileMutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Users]
      summary: Patch own user profile (JWT only)
      description: |
        Partial update of the authenticated JWT user's profile.
        Also available via PUT. No role permissions are required.
        This endpoint requires JWT Bearer authentication; API key authentication is rejected with `403`.
      operationId: patchMyUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MyUserProfileInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyUserProfileMutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/user-location:
    post:
      tags: [Users]
      summary: Submit GPS position (JWT only)
      description: |
        Stores a GPS position for the authenticated JWT user.
        The latest coordinates are also written to `user.location_lat` / `user.location_lng`
        for map visibility in order scheduling.
        This endpoint requires JWT Bearer authentication; API key authentication is rejected with `403`.
      operationId: createUserLocation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserLocationInput'
            examples:
              basic:
                summary: Current GPS position
                value:
                  latitude: 47.376886
                  longitude: 8.541694
                  accuracy: 12.5
                  recorded_at: "2026-06-22T14:30:00.000Z"
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserLocationMutationResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'

  /api/v3/settings:
    get:
      tags: [Settings]
      summary: List tenant settings
      description: |
        Returns all tenant settings as a key-value map.
        Sensitive keys (for example passwords, API keys, SMTP credentials, secrets, tokens) are omitted.
        Boolean values stored as `true`/`false` strings are returned as JSON booleans.

        **Required permission:** valid JWT Bearer token **or** `administrate` (API keys need `read_write_full`).

      operationId: listSettings
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettingsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/users:
    get:
      tags: [Users]
      summary: List all users
      description: |
        Returns a paginated list of all users.
        Supports full-text search (first name, last name, email, phone, zip, city).
        By default, only active users are returned.

        **Required permission:** `administrate_users` or `view_time_report_from_all_users`.

      operationId: listUsers
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Users]
      summary: Create user
      description: |
        **Required permission:** `administrate_users`.

        The fields `firstname`, `lastname`, and `email` are mandatory on create.
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingLastname:
                  summary: Fehlender Nachname
                  value:
                    status: 422
                    error: 422
                    messages:
                      lastname: "The lastname field is required."
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/users/{id}:
    get:
      tags: [Users]
      summary: Get single user
      description: "Required permission: `administrate_users` or `view_time_report_from_all_users`."
      operationId: getUser
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Users]
      summary: Update user
      description: "Also available via PATCH. Required permission: `administrate_users`."
      operationId: updateUser
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Users]
      summary: Patch user
      description: "Also available via PUT. Required permission: `administrate_users`."
      operationId: patchUser
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidEmail:
                  summary: Invalid email address
                  value:
                    status: 422
                    error: 422
                    messages:
                      email: "The email field must contain a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Users]
      summary: Delete user
      description: "Required permission: `administrate_users`."
      operationId: deleteUser
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Time Reports
  # ---------------------------------------------------------------------------
  /api/v3/time-reports:
    get:
      tags: [Time Reports]
      summary: List all time reports
      description: |
        Returns a paginated list of time reports from the `rapport` table.
        Supports full-text search and custom filters.

        **Required permission:** `view_time_report` or `view_time_report_from_all_users`.

      operationId: listTimeReports
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "77"
        - name: order_id
          in: query
          description: Filter by order ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1001"
        - name: user_id
          in: query
          description: Filter by user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "8"
        - name: article_id
          in: query
          description: Filter by article ID (comma-separated for multiple values)
          schema:
            type: string
            example: "12"
        - name: task_id
          in: query
          description: Filter by task ID (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: date
          in: query
          description: Filter by exact booking date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-22"
        - name: date_from
          in: query
          description: Inclusive start date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-01"
        - name: date_to
          in: query
          description: Inclusive end date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-30"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimeReportListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Time Reports]
      summary: Create time report
      description: |
        **Required permission:** `add_time_report` or `add_time_report_from_all_users`.

        `date` must be provided as `YYYY-MM-DD` (without time).
        For effort input, provide either:
        - `minutes`, or
        - both `time_from` and `time_to` (`HH:MM` or `HH:MM:SS`), then `minutes` is calculated automatically.
        `time_to` cannot be earlier than `time_from`.
        Typical field combinations in practice:
        - Ticket-based support booking (`support_ticket_id`, `reference_number`, `minutes`)
        - Work-order booking (`customer_id`, `project_id`, `order_id`, `article_id`, `task_id`, `minutes`)
        - Onsite block with explicit time range (`time_from`, `time_to` instead of `minutes`)
        Policy checks (legacy-compatible) may reject writes with `422`, for example:
        date protection windows, future/current-year restrictions, per-day/per-entry hour limits,
        protected/checked rapport flags, last-month lock, and absence on public holiday.
      operationId: createTimeReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportInput'
            examples:
              supportTicketByMinutes:
                summary: Support ticket with minutes
                value:
                  customer_id: 1042
                  project_id: 3201
                  order_id: 54012
                  support_ticket_id: 8841
                  reference_number: "SUP-2026-8841"
                  article_id: 701
                  task_id: 31
                  user_id: 23
                  date: "2026-04-15"
                  minutes: 90
                  unit_price: 165
                  description: "Incident analysis and email delivery fix"
              onsiteByTimeRange:
                summary: On-site work with time range
                value:
                  customer_id: 1178
                  project_id: 3201
                  order_id: 54012
                  article_id: 702
                  task_id: 31
                  user_id: 23
                  date: "2026-04-16"
                  time_from: "13:00"
                  time_to: "16:30"
                  minutes_allocatable: 210
                  rapport_factor: 1
                  description: "On-site deployment and handover test"
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
              examples:
                created:
                  summary: Successfully created
                  value:
                    id: 98156
        '422':
          description: Validation or policy error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingEffort:
                  summary: Neither minutes nor a time range provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      minutes: "Either minutes or both time_from and time_to must be provided."
                invalidTimeRange:
                  summary: End time precedes start time
                  value:
                    status: 422
                    error: 422
                    messages:
                      time_to: "time_to cannot be earlier than time_from."
                futureDateBlocked:
                  summary: Zukunftsdatum laut Richtlinie gesperrt
                  value:
                    status: 422
                    error: 422
                    messages:
                      date: "Time report date cannot be in the future."
                maxHoursPerDayExceeded:
                  summary: Daily limit exceeded
                  value:
                    status: 422
                    error: 422
                    messages:
                      minutes: "Maximal hours per user per day exceeded."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: No permission for this action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                error: 403
                messages:
                  error: "Forbidden"
              examples:
                noCreatePermission:
                  summary: Missing permission to create entry
                  value:
                    status: 403
                    error: 403
                    messages:
                      error: "Forbidden"

  /api/v3/my-time-reports:
    get:
      tags: [Time Reports]
      summary: List own time reports (JWT only)
      description: |
        Returns a paginated list of time reports for the authenticated JWT user only.
        Supports full-text search and custom filters (including `date`, `date_from`, and `date_to`).

        **Required permission:** `view_time_report` or `view_time_report_from_all_users`.

        This endpoint requires JWT Bearer authentication; API key authentication is rejected with `401`.
      operationId: listMyTimeReports
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1042,1178"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "3201"
        - name: order_id
          in: query
          description: Filter by order ID (comma-separated for multiple values)
          schema:
            type: string
            example: "54012"
        - name: user_id
          in: query
          description: Optional additional user filter. Results are always restricted to the authenticated JWT user.
          schema:
            type: string
            example: "23"
        - name: article_id
          in: query
          description: Filter by article ID (comma-separated for multiple values)
          schema:
            type: string
            example: "701,702"
        - name: task_id
          in: query
          description: Filter by task ID (comma-separated for multiple values)
          schema:
            type: string
            example: "31"
        - name: date
          in: query
          description: Filter by exact booking date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-15"
        - name: date_from
          in: query
          description: Inclusive start date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-01"
        - name: date_to
          in: query
          description: Inclusive end date (YYYY-MM-DD)
          schema:
            type: string
            format: date
            example: "2026-04-30"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimeReportListResponse'
              examples:
                ownReportsApril:
                  summary: Eigene Rapporte fuer April (JWT user_id=23)
                  value:
                    meta:
                      total: 2
                      total_filtered: 2
                      limit: 100
                      offset: 0
                      order_by: ""
                      search: ""
                    data:
                      - rapport_id: 98124
                        customer_id: 1042
                        project_id: 3201
                        order_id: 54012
                        order_article_id: null
                        support_ticket_id: 8841
                        reference_number: "SUP-2026-8841"
                        article_id: 701
                        task_id: 31
                        user_id: 23
                        rapport_tariff_id: 4
                        minutes: 90
                        minutes_allocatable: 90
                        unit_price: 165
                        flatrate_price: null
                        description: "Incident analysis and email delivery fix"
                        date: "2026-04-15"
                        time_from: "08:30:00"
                        time_to: "10:00:00"
                        is_invoiced: 0
                        invoice_id: null
                      - rapport_id: 98140
                        customer_id: 1178
                        project_id: 3201
                        order_id: 54012
                        order_article_id: null
                        support_ticket_id: null
                        reference_number: "OPS-APR-2026"
                        article_id: 702
                        task_id: 31
                        user_id: 23
                        rapport_tariff_id: 4
                        minutes: 45
                        minutes_allocatable: 45
                        unit_price: 165
                        description: "Monitoring checks and performance tuning"
                        date: "2026-04-16"
                        time_from: "16:00:00"
                        time_to: "16:45:00"
                        is_invoiced: 0
                        invoice_id: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/time-reports/{id}:
    get:
      tags: [Time Reports]
      summary: Get single time report
      description: |
        Returns one time report including relations and `rapport_expenses`.
        Includes `customer`, `project`, `order`, `user`, `article`, and `task`.

        **Required permission:** `view_time_report` or `view_time_report_from_all_users`.

      operationId: getTimeReport
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimeReportDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Time Reports]
      summary: Update time report
      description: |
        Also available via PATCH.

        **Required permission:** `edit_time_report` or `edit_time_report_from_all_users`.

        Effort input rules are identical to create:
        either `minutes`, or `time_from` + `time_to` (auto-calculates `minutes`).
        `date` must be `YYYY-MM-DD`.
        Applies the same policy checks as create and returns `422` on policy violations.
      operationId: updateTimeReport
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation or policy error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingEffort:
                  summary: Neither minutes nor a time range provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      minutes: "Either minutes or both time_from and time_to must be provided."
                invalidTimeRange:
                  summary: End time precedes start time
                  value:
                    status: 422
                    error: 422
                    messages:
                      time_to: "time_to cannot be earlier than time_from."
                futureDateBlocked:
                  summary: Zukunftsdatum laut Richtlinie gesperrt
                  value:
                    status: 422
                    error: 422
                    messages:
                      date: "Time report date cannot be in the future."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Time Reports]
      summary: Patch time report
      description: |
        Also available via PUT.

        **Required permission:** `edit_time_report` or `edit_time_report_from_all_users`.

        Effort input rules are identical to create:
        either `minutes`, or `time_from` + `time_to` (auto-calculates `minutes`).
        `date` must be `YYYY-MM-DD`.
        Applies the same policy checks as create and returns `422` on policy violations.
      operationId: patchTimeReport
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation or policy error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingEffort:
                  summary: Neither minutes nor a time range provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      minutes: "Either minutes or both time_from and time_to must be provided."
                invalidTimeRange:
                  summary: End time precedes start time
                  value:
                    status: 422
                    error: 422
                    messages:
                      time_to: "time_to cannot be earlier than time_from."
                futureDateBlocked:
                  summary: Zukunftsdatum laut Richtlinie gesperrt
                  value:
                    status: 422
                    error: 422
                    messages:
                      date: "Time report date cannot be in the future."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Time Reports]
      summary: Delete time report
      description: "Required permission: `remove_time_report`."
      operationId: deleteTimeReport
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Time Report Expenses
  # ---------------------------------------------------------------------------
  /api/v3/time-report-expenses:
    get:
      tags: [Time Report Expenses]
      summary: List time-report expenses
      description: |
        Returns a paginated list of expense rows from `rapport_expenses`.

        **Required permission:** `view_time_report` or `view_time_report_from_all_users`.

        **JWT scope restriction**: Without `view_time_report_from_all_users`, the
        `rapport_id` filter is required and the referenced rapport must belong to
        the authenticated user.
      operationId: listTimeReportExpenses
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - name: rapport_id
          in: query
          description: |
            Filter by time-report ID (comma-separated for multiple values).
            Required when using JWT without `view_time_report_from_all_users`.
          schema:
            type: string
            example: "9001"
        - name: expenses_id
          in: query
          description: Filter by expense definition ID (comma-separated for multiple values)
          schema:
            type: string
            example: "12"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimeReportExpenseListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: rapport_id missing (required under own-user JWT scope)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingRapportId:
                  summary: Missing rapport_id (JWT ownership scope)
                  value:
                    status: 422
                    error: 422
                    messages:
                      rapport_id: "rapport_id is required when accessing your own time-report expenses."
    post:
      tags: [Time Report Expenses]
      summary: Create time-report expense
      description: |
        Creates a new expense row linked to a time report.

        **Required permission:** `add_time_report` or `add_time_report_from_all_users`.

        `rapport_id` is required and must reference an existing time report.
        Under JWT without `add_time_report_from_all_users` the rapport must belong
        to the authenticated user.
        `expenses_id` is optional; when provided it must reference an existing expense definition.
      operationId: createTimeReportExpense
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportExpenseInput'
            examples:
              basic:
                summary: Expense with amount
                value:
                  rapport_id: 9001
                  expenses_id: 12
                  expenses: "Fahrtkosten"
                  quantity: 1
                  multiplier: 0.70
                  multiplier_external: 0.70
                  end_price: 42.00
                  comments: "Travel to customer"
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingRapportId:
                  summary: Missing rapport_id
                  value:
                    status: 422
                    error: 422
                    messages:
                      rapport_id: "rapport_id is required."
                rapportNotFound:
                  summary: Time report does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      rapport_id: "The provided rapport_id does not exist."
                expensesNotFound:
                  summary: Expense definition does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      expenses_id: "The provided expenses_id does not exist."
                invalidQuantity:
                  summary: Invalid quantity
                  value:
                    status: 422
                    error: 422
                    messages:
                      quantity: "The quantity field must be a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/time-report-expenses/{id}:
    get:
      tags: [Time Report Expenses]
      summary: Get single time-report expense
      description: |
        Returns one expense row including linked `rapport` and `expenses` relations.

        **Required permission:** `view_time_report` or `view_time_report_from_all_users`.

        Under JWT without `view_time_report_from_all_users`, the referenced rapport
        must belong to the authenticated user.
      operationId: getTimeReportExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimeReportExpenseDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Time Report Expenses]
      summary: Update time-report expense
      description: |
        Also available via PATCH. Partial update semantics for both PUT and PATCH.

        **Required permission:** `edit_time_report` or `edit_time_report_from_all_users`.

        Under JWT without `edit_time_report_from_all_users`, the existing rapport (and
        the new `rapport_id` if changed) must belong to the authenticated user.
      operationId: updateTimeReportExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportExpenseInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                rapportNotFound:
                  summary: Time report does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      rapport_id: "The provided rapport_id does not exist."
                expensesNotFound:
                  summary: Expense definition does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      expenses_id: "The provided expenses_id does not exist."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Time Report Expenses]
      summary: Patch time-report expense
      description: |
        Also available via PUT. Partial update semantics identical to PUT.

        **Required permission:** `edit_time_report` or `edit_time_report_from_all_users`.

      operationId: patchTimeReportExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimeReportExpenseInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                rapportNotFound:
                  summary: Time report does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      rapport_id: "The provided rapport_id does not exist."
                expensesNotFound:
                  summary: Expense definition does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      expenses_id: "The provided expenses_id does not exist."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Time Report Expenses]
      summary: Delete time-report expense
      description: |
        **Required permission:** `remove_time_report`.

        Under JWT, the referenced rapport must belong to the authenticated user
        (unless the user has `view_time_report_from_all_users`).
      operationId: deleteTimeReportExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Open Tasks
  # ---------------------------------------------------------------------------
  /api/v3/open-tasks:
    get:
      tags: [Open Tasks]
      summary: List open tasks
      description: |
        Returns a paginated list of open tasks with embedded relations.
        Supports full-text search across title, description, and notes fields.

        **Required permission:** `view_all_open_tasks` or `view_my_open_tasks`.

      operationId: listOpenTasks
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "15"
        - name: order_id
          in: query
          description: Filter by order ID (comma-separated for multiple values)
          schema:
            type: string
            example: "200"
        - name: open_task_state_id
          in: query
          description: Filter by task state ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: open_task_category_id
          in: query
          description: Filter by task category ID (comma-separated for multiple values)
          schema:
            type: string
            example: "3"
        - name: priority
          in: query
          description: "Filter by priority (0 = very low … 4 = very high)"
          schema:
            type: string
            example: "3"
        - name: user_id_responsible
          in: query
          description: Filter by responsible user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "7"
        - name: user_id_owner
          in: query
          description: Filter by owner user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: date_end_from
          in: query
          description: "Filter tasks with date_end on or after this date (format: YYYY-MM-DD)"
          schema:
            type: string
            format: date
            example: "2026-06-01"
        - name: date_end_to
          in: query
          description: "Filter tasks with date_end on or before this date (format: YYYY-MM-DD)"
          schema:
            type: string
            format: date
            example: "2026-06-30"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Open Tasks]
      summary: Create open task
      description: |
        Creates a new open task.

        **Required permission:** `add_open_task`.

        `title` and `customer_id` are required. `priority` defaults to 2 when omitted.
        `user_id_owner` is auto-filled from the JWT token when omitted.
        All FK fields (`project_id`, `order_id`, `contact_id`, `user_id_responsible`,
        `user_id_owner`, `open_task_state_id`, `open_task_category_id`) are existence-validated
        when provided.
      operationId: createOpenTask
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskCreateInput'
            examples:
              basic:
                summary: New task for customer
                value:
                  title: "Revise offer"
                  description: "Adjust line items 3 and 4 as requested by the customer"
                  customer_id: 42
                  project_id: 15
                  priority: 3
                  date_end: "2026-06-30"
                  open_task_state_id: 1
                  user_id_responsible: 7
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingTitle:
                  summary: Missing title
                  value:
                    status: 422
                    error: 422
                    messages:
                      title: "The title field is required."
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                customerNotFound:
                  summary: Customer does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The provided customer_id does not exist."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/open-tasks/{id}:
    get:
      tags: [Open Tasks]
      summary: Get single open task
      description: |
        Returns one open task with all embedded relations.

        **Required permission:** `view_all_open_tasks` or `view_my_open_tasks`.

      operationId: getOpenTask
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskItem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Open Tasks]
      summary: Update open task
      description: |
        Also available via PATCH. Partial update semantics for both PUT and PATCH.

        **Required permission:** `edit_all_open_task`, `edit_assigned_open_task`, or `edit_my_open_task`.

        All FK fields are existence-validated when provided.
      operationId: updateOpenTask
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskUpdateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                customerNotFound:
                  summary: Customer does not exist
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The provided customer_id does not exist."
                invalidPriority:
                  summary: Invalid priority
                  value:
                    status: 422
                    error: 422
                    messages:
                      priority: "The priority field must be one of: 0, 1, 2, 3, 4."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Open Tasks]
      summary: Patch open task
      description: |
        Also available via PUT. Partial update semantics identical to PUT.

        **Required permission:** `edit_all_open_task`, `edit_assigned_open_task`, or `edit_my_open_task`.

      operationId: patchOpenTask
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskUpdateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Open Tasks]
      summary: Delete open task
      description: |
        Permanently deletes an open task.

        **Required permission:** `remove_all_open_task`, `remove_assigned_open_task`, or `remove_my_open_task`.

      operationId: deleteOpenTask
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Support Tickets
  # ---------------------------------------------------------------------------
  /api/v3/support-tickets:
    get:
      tags: [Support Tickets]
      summary: List all support tickets
      description: |
        Returns a paginated list of all root tickets (excluding follow-ups).
        Each ticket includes a `history` array with the history thread (follow-up tickets).
        Supports full-text search (subject, question, email, internal comments) and various filters.

        **Required permission:** `support_ticket`.

      operationId: listSupportTickets
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: support_ticket_state_id
          in: query
          description: Filter by ticket state (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_id
          in: query
          description: Filter by customer (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: assigned_user_id
          in: query
          description: Filter by assigned user (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: priority
          in: query
          description: Filter by priority (comma-separated for multiple values)
          schema:
            type: string
            example: "1"
        - name: is_done
          in: query
          description: "Filter by done status (0 = open, 1 = done)"
          schema:
            type: string
            example: "0"
        - name: support_ticket_category_id
          in: query
          description: Filter by ticket category (comma-separated for multiple values)
          schema:
            type: string
            example: "3"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Support Tickets]
      summary: Create support ticket
      description: |
        **Required permission:** `add_support_ticket`.

        The fields `customer_id` and `email_from` are mandatory on create.
      operationId: createSupportTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupportTicketCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                missingEmailFrom:
                  summary: Missing sender email
                  value:
                    status: 422
                    error: 422
                    messages:
                      email_from: "The email_from field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/support-tickets/{id}:
    get:
      tags: [Support Tickets]
      summary: Get single support ticket
      description: |
        Returns a single ticket including the `history` array (history thread).

        **Required permission:** `support_ticket`.

      operationId: getSupportTicket
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketWithHistory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Support Tickets]
      summary: Update support ticket
      description: "Also available via PATCH. Required permission: `support_ticket` or `assign_support_tickets`."
      operationId: updateSupportTicket
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupportTicketInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidPriority:
                  summary: Invalid priority value
                  value:
                    status: 422
                    error: 422
                    messages:
                      priority: "The priority field must be one of: 1, 2, 3."
                invalidIsDone:
                  summary: Invalid is_done value
                  value:
                    status: 422
                    error: 422
                    messages:
                      is_done: "The is_done field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Support Tickets]
      summary: Patch support ticket
      description: "Also available via PUT. Required permission: `support_ticket` or `assign_support_tickets`."
      operationId: patchSupportTicket
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupportTicketInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidPriority:
                  summary: Invalid priority value
                  value:
                    status: 422
                    error: 422
                    messages:
                      priority: "The priority field must be one of: 1, 2, 3."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Support Tickets]
      summary: Delete support ticket
      description: "Required permission: `delete_support_ticket`."
      operationId: deleteSupportTicket
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/support-tickets/{id}/assign-to-user:
    post:
      tags: [Support Tickets]
      summary: Assign support ticket to user
      description: |
        Assigns a ticket to a user.
        Automatically sets `date_time_assigned` to the current timestamp.

        **Required permission:** `support_ticket` or `assign_support_tickets`.

      operationId: assignSupportTicketToUser
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupportTicketAssignToUserInput'
      responses:
        '201':
          description: Ticket assigned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketActionResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      user_id: "The user_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/support-tickets/{id}/answer:
    post:
      tags: [Support Tickets]
      summary: Answer support ticket
      description: |
        Adds/updates the answer on a ticket.
        Automatically sets `date_time_answered` to the current timestamp.
        `answer_user_id` may be passed explicitly; if omitted, it is resolved from JWT (`sub`).

        **Required permission:** `support_ticket` or `assign_support_tickets`.

      operationId: answerSupportTicket
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SupportTicketAnswerInput'
      responses:
        '201':
          description: Ticket answered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketActionResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      answer: "The answer field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/support-tickets/{id}/set-to-done:
    post:
      tags: [Support Tickets]
      summary: Mark support ticket as done
      description: |
        Marks the ticket as done (`is_done=1`) and sets `date_time_done` to the current timestamp.
        No request payload is required.

        **Required permission:** `support_ticket` or `assign_support_tickets`.

      operationId: setSupportTicketToDone
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '201':
          description: Ticket marked as done successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketActionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Comments
  # ---------------------------------------------------------------------------
  /api/v3/comments:
    get:
      tags: [Comments]
      summary: List comments
      description: |
        Returns a paginated list of comments for one linked resource.
        `link_type` and `link_id` are required query parameters.
        Access is restricted by the linked resource (`link_type`/`link_id`):
        only comments of areas with granted permissions are returned.
        Supports search on `comment`, `notify_by`.
      operationId: listComments
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: link_type
          in: query
          required: true
          description: Filter by linked resource type (`invoice`, `offer`, `order`, `support_ticket`, `customer`, `contact`, `project`)
          schema:
            type: string
            example: "offer"
        - name: link_id
          in: query
          required: true
          description: Filter by linked resource ID
          schema:
            type: string
            example: "5102"
        - name: creator_user_id
          in: query
          description: Filter by creator user ID
          schema:
            type: string
            example: "1"
        - name: assigned_user_id
          in: query
          description: Filter by assigned user ID (string value from legacy table)
          schema:
            type: string
            example: "118"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Comments]
      summary: Create comment
      description: |
        Creates a new comment.
        Required fields: `link_type`, `link_id`, `comment`.
        `creator_user_id` is auto-filled from JWT if omitted.
        `create_date_time` defaults to the current server datetime when omitted (or empty).
        `assigned_user_id` defaults to the JWT user id when omitted (or empty) and the request is JWT-authenticated.
        Write access is validated against the linked resource permissions.
      operationId: createComment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CommentInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingLinkType:
                  summary: Missing link_type
                  value:
                    status: 422
                    error: 422
                    messages:
                      link_type: "The link_type field is required."
                missingLinkId:
                  summary: Missing link_id
                  value:
                    status: 422
                    error: 422
                    messages:
                      link_id: "The link_id field is required."
                missingComment:
                  summary: Missing comment text
                  value:
                    status: 422
                    error: 422
                    messages:
                      comment: "The comment field is required."

  # ---------------------------------------------------------------------------
  # User absences (user_holiday_scheduling)
  # ---------------------------------------------------------------------------
  /api/v3/user-absences:
    get:
      tags: [User Absences]
      summary: List user absences
      description: |
        Returns a paginated list of absence entries from today to future (`user_holiday_scheduling`).
        Supports search on comments and filters by `user_id`, `absence`, and `date`.

        **Required permission:** `view_all_holiday_scheduling` or `view_department_holiday_scheduling`.

      operationId: listUserAbsences
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: user_id
          in: query
          description: Filter by user (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: absence
          in: query
          description: Filter by absence code (e.g. holiday)
          schema:
            type: string
            example: "holiday"
        - name: date
          in: query
          description: Filter absences for a specific day (`YYYY-MM-DD`), including multi-day absences.
          schema:
            type: string
            format: date
            pattern: '^\d{4}-\d{2}-\d{2}$'
            example: "2026-03-31"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserAbsenceListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/user-absences/{id}:
    get:
      tags: [User Absences]
      summary: Get single user absence
      description: |
        Returns one absence entry including embedded `user` object.

        **Required permission:** `view_all_holiday_scheduling` or `view_department_holiday_scheduling`.

      operationId: getUserAbsence
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserAbsence'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Salutations
  # ---------------------------------------------------------------------------
  /api/v3/salutations:
    get:
      tags: ["References: Salutations"]
      summary: List all salutations
      description: "Required permission: All authenticated users."
      operationId: listSalutations
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SalutationListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Salutations"]
      summary: Create salutation
      description: "Required permission: `administrate`."
      operationId: createSalutation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SalutationInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      salutation: "The salutation field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/salutations/{id}:
    get:
      tags: ["References: Salutations"]
      summary: Get single salutation
      description: "Required permission: `all`."
      operationId: getSalutation
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Salutation'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Salutations"]
      summary: Update salutation
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateSalutation
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SalutationInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      salutation: "The salutation field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Salutations"]
      summary: Patch salutation
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchSalutation
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SalutationInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      salutation: "The salutation field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Salutations"]
      summary: Delete salutation
      description: "Required permission: `administrate`."
      operationId: deleteSalutation
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Countries
  # ---------------------------------------------------------------------------
  /api/v3/countries:
    get:
      tags: ["References: Countries"]
      summary: List all countries
      description: "Required permission: All authenticated users."
      operationId: listCountries
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CountryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Countries"]
      summary: Create country
      description: "Required permission: `administrate`."
      operationId: createCountry
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CountryInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      country: "The country field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/countries/{id}:
    get:
      tags: ["References: Countries"]
      summary: Get single country
      description: "Required permission: `all`."
      operationId: getCountry
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Country'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Countries"]
      summary: Update country
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateCountry
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CountryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      country: "The country field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Countries"]
      summary: Patch country
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchCountry
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CountryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      country: "The country field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Countries"]
      summary: Delete country
      description: "Required permission: `administrate`."
      operationId: deleteCountry
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Cantons
  # ---------------------------------------------------------------------------
  /api/v3/cantons:
    get:
      tags: ["References: Cantons"]
      summary: List all cantons
      description: "Required permission: All authenticated users."
      operationId: listCantons
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CantonListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Cantons"]
      summary: Create canton
      description: "Required permission: `administrate`."
      operationId: createCanton
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CantonInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      canton: "The canton field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/cantons/{id}:
    get:
      tags: ["References: Cantons"]
      summary: Get single canton
      description: "Required permission: `all`."
      operationId: getCanton
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Canton'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Cantons"]
      summary: Update canton
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateCanton
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CantonInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      canton: "The canton field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Cantons"]
      summary: Patch canton
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchCanton
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CantonInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      canton: "The canton field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Cantons"]
      summary: Delete canton
      description: "Required permission: `administrate`."
      operationId: deleteCanton
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Currencies
  # ---------------------------------------------------------------------------
  /api/v3/currencies:
    get:
      tags: ["References: Currencies"]
      summary: List all currencies
      description: "Required permission: All authenticated users."
      operationId: listCurrencies
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrencyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Currencies"]
      summary: Create currency
      description: "Required permission: `administrate`."
      operationId: createCurrency
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CurrencyInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      conversion_rate: "The conversion_rate field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/currencies/{id}:
    get:
      tags: ["References: Currencies"]
      summary: Get single currency
      description: "Required permission: `all`."
      operationId: getCurrency
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Currency'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Currencies"]
      summary: Update currency
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateCurrency
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CurrencyInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      conversion_rate: "The conversion_rate field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Currencies"]
      summary: Patch currency
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchCurrency
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CurrencyInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      conversion_rate: "The conversion_rate field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Currencies"]
      summary: Delete currency
      description: "Required permission: `administrate`."
      operationId: deleteCurrency
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Departments
  # ---------------------------------------------------------------------------
  /api/v3/departments:
    get:
      tags: ["References: Departments"]
      summary: List all departments
      description: "Required permission: All authenticated users."
      operationId: listDepartments
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepartmentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Departments"]
      summary: Create department
      description: "Required permission: `administrate`."
      operationId: createDepartment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      department: "The department field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/departments/{id}:
    get:
      tags: ["References: Departments"]
      summary: Get single department
      description: "Required permission: `all`."
      operationId: getDepartment
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Department'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Departments"]
      summary: Update department
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateDepartment
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      department: "The department field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Departments"]
      summary: Patch department
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchDepartment
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepartmentInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      department: "The department field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Departments"]
      summary: Delete department
      description: "Required permission: `administrate`."
      operationId: deleteDepartment
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Places
  # ---------------------------------------------------------------------------
  /api/v3/places:
    get:
      tags: ["References: Places"]
      summary: List all places
      description: |
        **Required permission:** All authenticated users.

        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: listPlaces
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaceListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Places"]
      summary: Create place
      description: |
        **Required permission:** `administrate`.

        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: createPlace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      place: "The place field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/places/{id}:
    get:
      tags: ["References: Places"]
      summary: Get single place
      description: |
        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: getPlace
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Place'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Places"]
      summary: Update place
      description: |
        Also available via PATCH. Required permission: `administrate`.

        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: updatePlace
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Places"]
      summary: Patch place
      description: |
        Also available via PUT. Required permission: `administrate`.

        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: patchPlace
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Places"]
      summary: Delete place
      description: |
        **Required permission:** `administrate`.

        Only available when the tenant setting `using_places` is enabled; otherwise the API returns 403 Forbidden.
      operationId: deletePlace
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Encashments
  # ---------------------------------------------------------------------------
  /api/v3/encashments:
    get:
      tags: ["References: Encashments"]
      summary: List all encashments
      description: "Required permission: All authenticated users."
      operationId: listEncashments
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EncashmentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Encashments"]
      summary: Create encashment
      description: "Required permission: `administrate`."
      operationId: createEncashment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EncashmentInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      encashment: "The encashment field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/encashments/{id}:
    get:
      tags: ["References: Encashments"]
      summary: Get single encashment
      description: "Required permission: `all`."
      operationId: getEncashment
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Encashment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Encashments"]
      summary: Update encashment
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateEncashment
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EncashmentInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      encashment: "The encashment field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Encashments"]
      summary: Patch encashment
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchEncashment
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EncashmentInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      encashment: "The encashment field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Encashments"]
      summary: Delete encashment
      description: "Required permission: `administrate`."
      operationId: deleteEncashment
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Languages
  # ---------------------------------------------------------------------------
  /api/v3/languages:
    get:
      tags: ["References: Languages"]
      summary: List all languages
      description: "Required permission: All authenticated users."
      operationId: listLanguages
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LanguageListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Languages"]
      summary: Create language
      description: "Required permission: `administrate`."
      operationId: createLanguage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LanguageInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      language: "The language field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/languages/{id}:
    get:
      tags: ["References: Languages"]
      summary: Get single language
      description: "Required permission: `all`."
      operationId: getLanguage
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Language'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Languages"]
      summary: Update language
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateLanguage
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LanguageInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      language: "The language field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Languages"]
      summary: Patch language
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchLanguage
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LanguageInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      language: "The language field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Languages"]
      summary: Delete language
      description: "Required permission: `administrate`."
      operationId: deleteLanguage
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Absence Types
  # ---------------------------------------------------------------------------
  /api/v3/absence-types:
    get:
      tags: ["References: Absence Types"]
      summary: List all absence types
      description: "Required permission: All authenticated users."
      operationId: listAbsenceTypes
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbsenceTypeListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Absence Types"]
      summary: Create absence type
      description: "Required permission: `administrate`."
      operationId: createAbsenceType
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AbsenceTypeInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      absence_type: "The absence_type field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/absence-types/{id}:
    get:
      tags: ["References: Absence Types"]
      summary: Get single absence type
      description: "Required permission: `all`."
      operationId: getAbsenceType
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbsenceType'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Absence Types"]
      summary: Update absence type
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateAbsenceType
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AbsenceTypeInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      absence_type: "The absence_type field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Absence Types"]
      summary: Patch absence type
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchAbsenceType
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AbsenceTypeInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      absence_type: "The absence_type field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Absence Types"]
      summary: Delete absence type
      description: "Required permission: `administrate`."
      operationId: deleteAbsenceType
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Article Categories
  # ---------------------------------------------------------------------------
  /api/v3/article-categories:
    get:
      tags: ["References: Article Categories"]
      summary: List all article categories
      description: "Required permission: All authenticated users."
      operationId: listArticleCategories
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleCategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Article Categories"]
      summary: Create article category
      description: "Required permission: `administrate`."
      operationId: createArticleCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleCategoryInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_category: "The article_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/article-categories/{id}:
    get:
      tags: ["References: Article Categories"]
      summary: Get single article category
      description: "Required permission: `all`."
      operationId: getArticleCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleCategory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Article Categories"]
      summary: Update article category
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateArticleCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_category: "The article_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Article Categories"]
      summary: Patch article category
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchArticleCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_category: "The article_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Article Categories"]
      summary: Delete article category
      description: "Required permission: `administrate`."
      operationId: deleteArticleCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Article Taxes
  # ---------------------------------------------------------------------------
  /api/v3/article-taxes:
    get:
      tags: ["References: Article Taxes"]
      summary: List all article taxes
      description: "Required permission: All authenticated users."
      operationId: listArticleTaxes
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleTaxListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Article Taxes"]
      summary: Create article tax
      description: "Required permission: `administrate`."
      operationId: createArticleTax
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleTaxInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_tax_percentage: "The article_tax_percentage field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/article-taxes/{id}:
    get:
      tags: ["References: Article Taxes"]
      summary: Get single article tax
      description: "Required permission: `all`."
      operationId: getArticleTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleTax'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Article Taxes"]
      summary: Update article tax
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateArticleTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleTaxInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_tax_percentage: "The article_tax_percentage field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Article Taxes"]
      summary: Patch article tax
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchArticleTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleTaxInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_tax_percentage: "The article_tax_percentage field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Article Taxes"]
      summary: Delete article tax
      description: "Required permission: `administrate`."
      operationId: deleteArticleTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Article Units
  # ---------------------------------------------------------------------------
  /api/v3/article-units:
    get:
      tags: ["References: Article Units"]
      summary: List all article units
      description: "Required permission: All authenticated users."
      operationId: listArticleUnits
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleUnitListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Article Units"]
      summary: Create article unit
      description: "Required permission: `administrate`."
      operationId: createArticleUnit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleUnitInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_unit: "The article_unit field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/article-units/{id}:
    get:
      tags: ["References: Article Units"]
      summary: Get single article unit
      description: "Required permission: `all`."
      operationId: getArticleUnit
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleUnit'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Article Units"]
      summary: Update article unit
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateArticleUnit
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleUnitInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_unit: "The article_unit field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Article Units"]
      summary: Patch article unit
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchArticleUnit
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArticleUnitInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_unit: "The article_unit field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Article Units"]
      summary: Delete article unit
      description: "Required permission: `administrate`."
      operationId: deleteArticleUnit
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Bank Accounts
  # ---------------------------------------------------------------------------
  /api/v3/bank-accounts:
    get:
      tags: ["References: Bank Accounts"]
      summary: List all bank accounts
      description: "Required permission: All authenticated users."
      operationId: listBankAccounts
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BankAccountListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Bank Accounts"]
      summary: Create bank account
      description: "Required permission: `administrate`."
      operationId: createBankAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BankAccountInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      bank_account: "The bank_account field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/bank-accounts/{id}:
    get:
      tags: ["References: Bank Accounts"]
      summary: Get single bank account
      description: "Required permission: `all`."
      operationId: getBankAccount
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BankAccount'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Bank Accounts"]
      summary: Update bank account
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateBankAccount
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BankAccountInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      bank_account: "The bank_account field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Bank Accounts"]
      summary: Patch bank account
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchBankAccount
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BankAccountInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      bank_account: "The bank_account field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Bank Accounts"]
      summary: Delete bank account
      description: "Required permission: `administrate`."
      operationId: deleteBankAccount
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Email Templates
  # ---------------------------------------------------------------------------
  /api/v3/email-templates:
    get:
      tags: ["References: Email Templates"]
      summary: List all email templates
      description: "Required permission: All authenticated users."
      operationId: listEmailTemplates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailTemplateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Email Templates"]
      summary: Create email template
      description: "Required permission: `administrate`."
      operationId: createEmailTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailTemplateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      email_template: "The email_template field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/email-templates/{id}:
    get:
      tags: ["References: Email Templates"]
      summary: Get single email template
      description: "Required permission: `all`."
      operationId: getEmailTemplate
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailTemplate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Email Templates"]
      summary: Update email template
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateEmailTemplate
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailTemplateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      email_template: "The email_template field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Email Templates"]
      summary: Patch email template
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchEmailTemplate
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailTemplateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      email_template: "The email_template field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Email Templates"]
      summary: Delete email template
      description: "Required permission: `administrate`."
      operationId: deleteEmailTemplate
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Offers
  # ---------------------------------------------------------------------------
  /api/v3/offers:
    get:
      tags: [Offers]
      summary: List all offers
      description: |
        Returns a paginated list of offers using only fields from the base `offer` table.
        Supports full-text search and custom filters (`state_id`, `customer_id`, `contact_id`, `project_id`, `order_category_id`, `user_id`).

        **Required permission:** `view_offers`.

        Under restricted read access, results are automatically limited to calendar years only
        (default: current year + previous year via API setting `restrictedBusinessReadYears=2`).
      operationId: listOffers
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: state_id
          in: query
          description: Filter by state ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: contact_id
          in: query
          description: Filter by contact ID (comma-separated for multiple values)
          schema:
            type: string
            example: "100"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "77"
        - name: order_category_id
          in: query
          description: Filter by order category ID (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: user_id
          in: query
          description: Filter by responsible user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "8"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Offers]
      summary: Create offer
      description: |
        **Required permission:** `add_offer`.

        Expects `articles` as JSON array and synchronizes `offer_article` rows including total amount recalculation.
        On create, `customer_id` and at least one `articles` row are required.
        If `offer` is omitted/empty, the configured default label is used.
        If `state_id` is omitted, the first state by `sort` is used.
        If `currency_id` is omitted, the currency with `standard=1` is used.
        If `user_id` is omitted and writable, it is automatically set from the JWT user (`sub`).
      operationId: createOffer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                missingArticles:
                  summary: No articles provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      articles: "At least one article is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/offers/{id}:
    get:
      tags: [Offers]
      summary: Get single offer
      description: |
        Returns a single offer enriched with detail relations.
        Includes `articles`, optional `optional_articles`, `offer_history`, `customer`, `contact`, `project`, `order_category`, and `state`.

        **Required permission:** `view_offers`.

        Under restricted read access, only entries within the configured calendar-year window are delivered
        (default: current year + previous year via setting `restrictedBusinessReadYears=2`).
      operationId: getOffer
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Offers]
      summary: Update offer
      description: |
        Also available via PATCH. Required permission: `edit_offer`.
        If `articles` is provided, all existing `offer_article` rows are replaced and totals are recalculated.
      operationId: updateOffer
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingArticles:
                  summary: Fehlende articles-Struktur
                  value:
                    status: 422
                    error: 422
                    messages:
                      articles: "The articles field must be an array."
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Offers]
      summary: Patch offer
      description: |
        Also available via PUT. Required permission: `edit_offer`.
        If `articles` is provided, all existing article positions are replaced and totals are recalculated.
      operationId: patchOffer
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Offers]
      summary: Delete offer
      description: "Required permission: `remove_offer`."
      operationId: deleteOffer
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/offers/{id}/articles:
    post:
      tags: [Offers]
      summary: Add article to offer
      description: |
        Adds exactly one article to an existing offer and recalculates totals.
        `is_optional=1` marks the row as optional.
        Totals are calculated from non-optional positions only.

        **Required permission:** `edit_offer`.

      operationId: addOfferArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BusinessDocumentArticleInput'
      responses:
        '201':
          description: Article added successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_id: "The article_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/offers/{id}/send-email:
    post:
      tags: [Offers]
      summary: Queue offer email
      description: |
        Queues an offer email delivery in `document_email_queue`.
        The row is processed asynchronously by the yourBureau cron job; delivery may therefore be delayed by a few minutes.

        Defaults when omitted:
        - `to`: contact or customer email from the offer
        - `subject` / `body`: default active `email_template` for offers
        - `template_id` / `template_ids`: PDF templates from settings `default_offer_email_templates` at send time
        - `state_id`: document state from settings `default_offer_email_state_id` after successful send

        **Required permission:** `edit_offer`.

      operationId: sendOfferEmail
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentEmailSendInput'
            examples:
              withOverrides:
                summary: With optional overrides
                value:
                  template_id: 12
                  to: "kunde@example.org"
                  cc: "team@example.org"
                  bcc: "audit@example.org"
                  subject: "Your quote"
                  body: "We are pleased to send you our quote."
                  state_id: 3
              defaults:
                summary: All defaults from document and settings
                value: {}
      responses:
        '201':
          description: Email delivery queued successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentEmailQueueResponse'
              examples:
                queued:
                  summary: Offer email queued
                  value:
                    id: 42
                    status: success
                    message: Offer email queued successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/offers-state:
    patch:
      tags: [Offers]
      summary: Change offer state
      description: "Required permission: `edit_offer`."
      operationId: changeOfferState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferStateChangeInput'
      responses:
        '200':
          description: State changed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      offer_state_id: "The offer_state_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Orders
  # ---------------------------------------------------------------------------
  /api/v3/orders:
    get:
      tags: [Orders]
      summary: List all orders
      description: |
        Returns a paginated list of orders using only fields from the base `order` table.
        Supports full-text search and custom filters.

        **Required permission:** `view_orders` or `limited_view_orders`.

        With `limited_view_orders`, sensitive amount fields (e.g. `amount*`) are omitted from the response.
        Under restricted read access, results are automatically limited to calendar years only
        (default: current year + previous year via API setting `restrictedBusinessReadYears=2`).
      operationId: listOrders
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: state_id
          in: query
          description: Filter by state ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: contact_id
          in: query
          description: Filter by contact ID (comma-separated for multiple values)
          schema:
            type: string
            example: "100"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "77"
        - name: order_category_id
          in: query
          description: Filter by order category ID (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: user_id
          in: query
          description: Filter by responsible user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "8"
        - name: priority
          in: query
          description: "Filter by priority (0 = very low … 4 = very high; comma-separated for multiple values)"
          schema:
            type: string
            example: "2,3"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Orders]
      summary: Create order
      description: |
        **Required permission:** `add_order`.

        Expects `articles` as JSON array and synchronizes `order_article` rows including total amount recalculation.
        On create, `customer_id` and at least one `articles` row are required.
        If `order` is omitted/empty, the configured default label is used.
        If `state_id` is omitted, the first state by `sort` is used.
        If `currency_id` is omitted, the currency with `standard=1` is used.
        If `user_id` is omitted and writable, it is automatically set from the JWT user (`sub`).
      operationId: createOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                missingArticles:
                  summary: No articles provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      articles: "At least one article is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/orders/{id}:
    get:
      tags: [Orders]
      summary: Get single order
      description: |
        Returns a single order enriched with detail relations.
        Includes `articles`, optional `optional_articles`, `order_history`, `customer`, `contact`, `project`, `order_category`, `order_scheduling_category`, and `state`.

        **Required permission:** `view_orders` or `limited_view_orders`.

        With `limited_view_orders`, sensitive amount fields (e.g. `amount*`) are omitted from the response.
        Under restricted read access, only entries within the configured calendar-year window are delivered
        (default: current year + previous year via setting `restrictedBusinessReadYears=2`).
      operationId: getOrder
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Orders]
      summary: Update order
      description: |
        Also available via PATCH. Required permission: `edit_order`.
        If `articles` is provided, all existing article positions are replaced and totals are recalculated.
      operationId: updateOrder
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Orders]
      summary: Patch order
      description: |
        Also available via PUT. Required permission: `edit_order`.
        If `articles` is provided, all existing article positions are replaced and totals are recalculated.
      operationId: patchOrder
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Orders]
      summary: Delete order
      description: "Required permission: `remove_order`."
      operationId: deleteOrder
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/orders/{id}/articles:
    post:
      tags: [Orders]
      summary: Add article to order
      description: |
        Adds exactly one article to an existing order and recalculates totals.
        `is_optional=1` marks the row as optional.
        Totals are calculated from non-optional positions only.

        **Required permission:** `edit_order`.

      operationId: addOrderArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BusinessDocumentArticleInput'
      responses:
        '201':
          description: Article added successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_id: "The article_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/orders/{id}/send-email:
    post:
      tags: [Orders]
      summary: Queue order email
      description: |
        Queues an order email delivery in `document_email_queue`.
        The row is processed asynchronously by the yourBureau cron job; delivery may therefore be delayed by a few minutes.

        Defaults when omitted:
        - `to`: contact or customer email from the order
        - `subject` / `body`: default active `email_template` for orders
        - `template_id` / `template_ids`: PDF templates from settings `default_order_email_templates` at send time
        - `state_id`: document state from settings `default_order_email_state_id` after successful send

        **Required permission:** `edit_order`.

      operationId: sendOrderEmail
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentEmailSendInput'
            examples:
              withOverrides:
                summary: With optional overrides
                value:
                  template_id: 12
                  to: "kunde@example.org"
                  cc: "team@example.org"
                  bcc: "audit@example.org"
                  subject: "Your order"
                  body: "Thank you for your order."
                  state_id: 3
              defaults:
                summary: All defaults from document and settings
                value: {}
      responses:
        '201':
          description: Email delivery queued successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentEmailQueueResponse'
              examples:
                queued:
                  summary: Order email queued
                  value:
                    id: 42
                    status: success
                    message: Order email queued successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/orders-state:
    patch:
      tags: [Orders]
      summary: Change order state
      description: "Required permission: `edit_order`."
      operationId: changeOrderState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderStateChangeInput'
      responses:
        '200':
          description: State changed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_state_id: "The order_state_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Invoices
  # ---------------------------------------------------------------------------
  /api/v3/invoices:
    get:
      tags: [Invoices]
      summary: List all invoices
      description: |
        Returns a paginated list of invoices using only fields from the base `invoice` table.
        Supports full-text search and custom filters.

        **Required permission:** `view_invoices`.

        Under restricted read access, results are automatically limited to calendar years only
        (default: current year + previous year via API setting `restrictedBusinessReadYears=2`).
      operationId: listInvoices
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - $ref: '#/components/parameters/searchParam'
        - name: state_id
          in: query
          description: Filter by state ID (comma-separated for multiple values)
          schema:
            type: string
            example: "1,2"
        - name: customer_id
          in: query
          description: Filter by customer ID (comma-separated for multiple values)
          schema:
            type: string
            example: "42"
        - name: contact_id
          in: query
          description: Filter by contact ID (comma-separated for multiple values)
          schema:
            type: string
            example: "100"
        - name: project_id
          in: query
          description: Filter by project ID (comma-separated for multiple values)
          schema:
            type: string
            example: "77"
        - name: order_category_id
          in: query
          description: Filter by order category ID (comma-separated for multiple values)
          schema:
            type: string
            example: "5"
        - name: user_id
          in: query
          description: Filter by responsible user ID (comma-separated for multiple values)
          schema:
            type: string
            example: "8"
        - name: is_canceled
          in: query
          description: Filter canceled status (0 or 1; comma-separated for multiple values)
          schema:
            type: string
            example: "0"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Invoices]
      summary: Create invoice
      description: |
        **Required permission:** `add_invoice`.

        ### Article processing

        Expects `articles` as a JSON array. The corresponding `invoice_article`
        rows are synchronized and the invoice total is recalculated.

        ### Required on creation

        - `customer_id`
        - At least one row in `articles`

        ### Automatic defaults

        - `invoice`: Configured default label when omitted or empty.
        - `state_id`: First state by `sort` when omitted.
        - `encashment_id`: Default encashment when omitted.
        - `currency_id`: Currency with `standard=1` when omitted.
        - `payment_terms`: Customer's `customer_payment_terms` when set and greater than zero; otherwise 30. Zero is treated as unset.
        - `user_id`: JWT user (`sub`) when omitted and the field is writable.

        `date_pay` is not writable via create/update; it is set on payment
        (`POST /api/v3/invoices-payment`) or in yourBureau when the invoice is paid.
      operationId: createInvoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceCreateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingCustomer:
                  summary: Missing customer ID
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_id: "The customer_id field is required."
                missingArticles:
                  summary: No articles provided
                  value:
                    status: 422
                    error: 422
                    messages:
                      articles: "At least one article is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/invoices/{id}:
    get:
      tags: [Invoices]
      summary: Get single invoice
      description: |
        Returns a single invoice enriched with detail relations.
        Includes `articles`, `invoice_history`, `invoice_payments`, `customer`, `contact`, `project`, `order_category`, and `state`.

        **Required permission:** `view_invoices`.

        Under restricted read access, only entries within the configured calendar-year window are delivered
        (default: current year + previous year via setting `restrictedBusinessReadYears=2`).
      operationId: getInvoice
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Invoices]
      summary: Update invoice
      description: |
        Also available via PATCH. Required permission: `edit_invoice`.
        If `articles` is provided, all existing article positions are replaced and totals are recalculated.
        Update is blocked once `accounting_export_date_time` is set.
      operationId: updateInvoice
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Invoices]
      summary: Patch invoice
      description: |
        Also available via PUT. Required permission: `edit_invoice`.
        If `articles` is provided, all existing article positions are replaced and totals are recalculated.
        Update is blocked once `accounting_export_date_time` is set.
      operationId: patchInvoice
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidArticlePrice:
                  summary: Invalid article price in line item
                  value:
                    status: 422
                    error: 422
                    messages:
                      "articles.0.article_price": "The article_price field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Invoices]
      summary: Delete invoice
      description: |
        **Required permission:** `remove_invoice`.

        Delete is blocked once `accounting_export_date_time` is set (except `null`, empty string, or `0000-00-00 00:00:00`).
      operationId: deleteInvoice
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/invoices/{id}/articles:
    post:
      tags: [Invoices]
      summary: Add article to invoice
      description: |
        Adds exactly one article to an existing invoice and recalculates totals.
        Allowed only when the invoice state is one of the configured special states (default: `open`).
        Optional articles are not allowed for invoices.
        Totals are calculated from non-optional positions only.

        **Required permission:** `edit_invoice`.

      operationId: addInvoiceArticle
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BusinessDocumentArticleInput'
      responses:
        '201':
          description: Article added successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      article_id: "The article_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/invoices/{id}/send-email:
    post:
      tags: [Invoices]
      summary: Queue invoice email
      description: |
        Queues an invoice email delivery in `document_email_queue`.
        The row is processed asynchronously by the yourBureau cron job; delivery may therefore be delayed by a few minutes.

        Defaults when omitted:
        - `to`: contact or customer email from the invoice
        - `subject` / `body`: default active `email_template` for invoices
        - `template_id` / `template_ids`: PDF templates from settings `default_invoice_email_templates` at send time
        - `state_id`: document state from settings `default_invoice_email_state_id` after successful send

        **Required permission:** `edit_invoice`.

      operationId: sendInvoiceEmail
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentEmailSendInput'
            examples:
              withOverrides:
                summary: With optional overrides
                value:
                  template_id: 12
                  to: "kunde@example.org"
                  cc: "team@example.org"
                  bcc: "audit@example.org"
                  subject: "Your invoice"
                  body: "Anbei erhalten Sie Your invoice."
                  state_id: 3
              defaults:
                summary: All defaults from document and settings
                value: {}
      responses:
        '201':
          description: Email delivery queued successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentEmailQueueResponse'
              examples:
                queued:
                  summary: Invoice email queued
                  value:
                    id: 42
                    status: success
                    message: Invoice email queued successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/invoices-payment:
    get:
      tags: [Invoices]
      summary: List invoice payments
      description: |
        Returns all payment rows for one invoice, ordered by payment date ascending.
        Query parameter `invoice_id` is required.
        Same restricted history window as invoice detail: invoices outside the window yield `404`.

        **Required permission:** `view_invoices`.
      operationId: listInvoicePayments
      parameters:
        - name: invoice_id
          in: query
          required: true
          description: Invoice ID whose payments to list
          schema:
            type: integer
            example: 4201
      responses:
        '200':
          description: Payment rows for the invoice (empty array when none exist)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/InvoicePayment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingInvoiceId:
                  summary: Missing or invalid invoice_id
                  value:
                    status: 422
                    error: 422
                    messages:
                      invoice_id: "A valid invoice_id is required."
    post:
      tags: [Invoices]
      summary: Add invoice payment
      description: |
        Creates one payment row in `invoice_payment` for the invoice.
        Sets invoice `date_pay` to the payment `date` when `date_pay` is still empty.
        Optional `invoice_state_id` updates invoice state after payment creation.

        **Required permission:** `edit_invoice`.

      operationId: addInvoicePayment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoicePaymentInput'
      responses:
        '201':
          description: Payment created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      payment_amount: "The payment_amount field must contain a decimal number."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/invoices-state:
    patch:
      tags: [Invoices]
      summary: Change invoice state
      description: "Required permission: `edit_invoice`."
      operationId: changeInvoiceState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceStateChangeInput'
      responses:
        '200':
          description: State changed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      invoice_state_id: "The invoice_state_id field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Assistant
  # ---------------------------------------------------------------------------
  /api/v3/assistant/info-query/catalog:
    get:
      tags: [Assistant]
      summary: List allowlisted info query types
      description: |
        Returns KPI/query types available to the current principal.
        Required permission (any): `view_invoices`, `view_offers`, `view_orders`, `view_my_offers`, `view_my_orders`, or `view_customers`.
      operationId: listAssistantInfoQueryCatalog
      responses:
        '200':
          description: Catalog of query types
          content:
            application/json:
              schema:
                type: object
                properties:
                  query_types:
                    type: array
                    items:
                      type: object
                      properties:
                        query_type: { type: string }
                        description: { type: string }
                        params:
                          type: array
                          items: { type: string }
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/assistant/info-query:
    get:
      tags: [Assistant]
      summary: Execute an info query (GET)
      description: |
        Whitelisted KPI query. Prefer GET for read-only API keys.
        Required permission depends on `query_type` (see catalog).
      operationId: executeAssistantInfoQueryGet
      parameters:
        - name: query_type
          in: query
          required: true
          schema: { type: string }
        - name: year
          in: query
          schema: { type: integer }
        - name: month
          in: query
          schema: { type: integer }
        - name: quarter
          in: query
          schema: { type: integer }
        - name: limit
          in: query
          schema: { type: integer }
        - name: project_id
          in: query
          schema: { type: integer }
        - name: order_id
          in: query
          schema: { type: integer }
        - name: customer_id
          in: query
          schema: { type: integer }
        - name: customer_name
          in: query
          schema: { type: string }
        - name: compare
          in: query
          schema:
            type: string
            enum: [previous_year, previous_quarter]
      responses:
        '200':
          description: Query result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantInfoQueryResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: [Assistant]
      summary: Execute an info query (POST)
      description: Same as GET; body may nest params under `params`.
      operationId: executeAssistantInfoQueryPost
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [query_type]
              properties:
                query_type: { type: string }
                params:
                  type: object
                  additionalProperties: true
                year: { type: integer }
                month: { type: integer }
                quarter: { type: integer }
                limit: { type: integer }
                project_id: { type: integer }
                order_id: { type: integer }
                customer_id: { type: integer }
                customer_name: { type: string }
                compare:
                  type: string
                  enum: [previous_year, previous_quarter]
      responses:
        '200':
          description: Query result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantInfoQueryResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/assistant/documents:
    post:
      tags: [Assistant]
      summary: Create offer, order, or invoice via assistant
      description: |
        Creates a business document with at least one article.
        Required permission (any): `add_offer`, `add_order`, or `add_invoice`.
        Invoice payment/reminder/storno operations are blocked.
      operationId: createAssistantDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssistantDocumentCreateInput'
      responses:
        '201':
          description: Document created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/assistant/documents/{type}/{id}:
    get:
      tags: [Assistant]
      summary: Get assistant document
      operationId: getAssistantDocument
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      responses:
        '200':
          description: Document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Assistant]
      summary: Update assistant document
      operationId: updateAssistantDocumentPut
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '422':
          $ref: '#/components/responses/ValidationError'
    patch:
      tags: [Assistant]
      summary: Update assistant document (partial)
      operationId: updateAssistantDocumentPatch
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/assistant/documents/{type}/{id}/customer:
    post:
      tags: [Assistant]
      summary: Set document customer
      operationId: setAssistantDocumentCustomer
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customer_id]
              properties:
                customer_id: { type: integer }
      responses:
        '200':
          description: Customer set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/assistant/documents/{type}/{id}/positions:
    post:
      tags: [Assistant]
      summary: Replace document article positions
      operationId: mutateAssistantDocumentPositions
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [articles]
              properties:
                mode: { type: string, enum: [replace] }
                articles:
                  type: array
                  items: { type: object, additionalProperties: true }
      responses:
        '200':
          description: Positions updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v3/assistant/documents/{type}/{id}/finalize:
    post:
      tags: [Assistant]
      summary: Finalize document (requires confirm)
      description: "Requires `confirm: true`. Optional `state_id` to change state on finalize."
      operationId: finalizeAssistantDocument
      parameters:
        - $ref: '#/components/parameters/AssistantDocumentType'
        - $ref: '#/components/parameters/AssistantDocumentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [confirm]
              properties:
                confirm: { type: boolean }
                state_id: { type: integer }
      responses:
        '200':
          description: Finalized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistantDocumentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

  # ---------------------------------------------------------------------------
  # Offer States
  # ---------------------------------------------------------------------------
  /api/v3/offer-states:
    get:
      tags: ["References: Offer States"]
      summary: List all offer states
      description: "Required permission: All authenticated users."
      operationId: listOfferStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Offer States"]
      summary: Create offer state
      description: "Required permission: `administrate`."
      operationId: createOfferState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      offer_state: "The offer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/offer-states/{id}:
    get:
      tags: ["References: Offer States"]
      summary: Get single offer state
      description: "Required permission: `all`."
      operationId: getOfferState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Offer States"]
      summary: Update offer state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOfferState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      offer_state: "The offer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Offer States"]
      summary: Patch offer state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOfferState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OfferStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      offer_state: "The offer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Offer States"]
      summary: Delete offer state
      description: "Required permission: `administrate`."
      operationId: deleteOfferState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Order Categories
  # ---------------------------------------------------------------------------
  /api/v3/order-categories:
    get:
      tags: ["References: Order Categories"]
      summary: List all order categories
      description: "Required permission: All authenticated users."
      operationId: listOrderCategories
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderCategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Order Categories"]
      summary: Create order category
      description: "Required permission: `administrate`."
      operationId: createOrderCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderCategoryInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_category: "The order_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/order-categories/{id}:
    get:
      tags: ["References: Order Categories"]
      summary: Get single order category
      description: "Required permission: `all`."
      operationId: getOrderCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderCategory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Order Categories"]
      summary: Update order category
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOrderCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_category: "The order_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Order Categories"]
      summary: Patch order category
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOrderCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_category: "The order_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Order Categories"]
      summary: Delete order category
      description: "Required permission: `administrate`."
      operationId: deleteOrderCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Order Scheduling Categories
  # ---------------------------------------------------------------------------
  /api/v3/order-scheduling-categories:
    get:
      tags: ["References: Order Scheduling Categories"]
      summary: List all order scheduling categories
      description: "Required permission: All authenticated users."
      operationId: listOrderSchedulingCategories
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderSchedulingCategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Order Scheduling Categories"]
      summary: Create order scheduling category
      description: "Required permission: `administrate`."
      operationId: createOrderSchedulingCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderSchedulingCategoryInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_scheduling_category: "The order_scheduling_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/order-scheduling-categories/{id}:
    get:
      tags: ["References: Order Scheduling Categories"]
      summary: Get single order scheduling category
      description: "Required permission: `all`."
      operationId: getOrderSchedulingCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderSchedulingCategory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Order Scheduling Categories"]
      summary: Update order scheduling category
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOrderSchedulingCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderSchedulingCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_scheduling_category: "The order_scheduling_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Order Scheduling Categories"]
      summary: Patch order scheduling category
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOrderSchedulingCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderSchedulingCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_scheduling_category: "The order_scheduling_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Order Scheduling Categories"]
      summary: Delete order scheduling category
      description: "Required permission: `administrate`."
      operationId: deleteOrderSchedulingCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Order States
  # ---------------------------------------------------------------------------
  /api/v3/order-states:
    get:
      tags: ["References: Order States"]
      summary: List all order states
      description: "Required permission: All authenticated users."
      operationId: listOrderStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Order States"]
      summary: Create order state
      description: "Required permission: `administrate`."
      operationId: createOrderState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_state: "The order_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/order-states/{id}:
    get:
      tags: ["References: Order States"]
      summary: Get single order state
      description: "Required permission: `all`."
      operationId: getOrderState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Order States"]
      summary: Update order state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOrderState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_state: "The order_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Order States"]
      summary: Patch order state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOrderState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      order_state: "The order_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Order States"]
      summary: Delete order state
      description: "Required permission: `administrate`."
      operationId: deleteOrderState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Invoice States
  # ---------------------------------------------------------------------------
  /api/v3/invoice-states:
    get:
      tags: ["References: Invoice States"]
      summary: List all invoice states
      description: "Required permission: All authenticated users."
      operationId: listInvoiceStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Invoice States"]
      summary: Create invoice state
      description: "Required permission: `administrate`."
      operationId: createInvoiceState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      invoice_state: "The invoice_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/invoice-states/{id}:
    get:
      tags: ["References: Invoice States"]
      summary: Get single invoice state
      description: "Required permission: `all`."
      operationId: getInvoiceState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Invoice States"]
      summary: Update invoice state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateInvoiceState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      invoice_state: "The invoice_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Invoice States"]
      summary: Patch invoice state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchInvoiceState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      invoice_state: "The invoice_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Invoice States"]
      summary: Delete invoice state
      description: "Required permission: `administrate`."
      operationId: deleteInvoiceState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Customer States
  # ---------------------------------------------------------------------------

  /api/v3/outgoing-states:
    get:
      tags: ["References: Outgoing States"]
      summary: List all outgoing states
      description: "Required permission: All authenticated users."
      operationId: listOutgoingStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Outgoing States"]
      summary: Create outgoing state
      description: "Required permission: `administrate`."
      operationId: createOutgoingState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      outgoing_state: "The outgoing_state field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/outgoing-states/{id}:
    get:
      tags: ["References: Outgoing States"]
      summary: Get single outgoing state
      description: "Required permission: `all`."
      operationId: getOutgoingState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Outgoing States"]
      summary: Update outgoing state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOutgoingState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid color
                  value:
                    status: 422
                    error: 422
                    messages:
                      color: "The color field must be a valid hexadecimal value."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Outgoing States"]
      summary: Patch outgoing state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOutgoingState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      outgoing_state: "The outgoing_state field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Outgoing States"]
      summary: Delete outgoing state
      description: "Required permission: `administrate`."
      operationId: deleteOutgoingState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/outgoing-taxes:
    get:
      tags: ["References: Outgoing Taxes"]
      summary: List all outgoing taxes
      description: "Required permission: All authenticated users."
      operationId: listOutgoingTaxes
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingTaxListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Outgoing Taxes"]
      summary: Create outgoing tax
      description: "Required permission: `administrate`."
      operationId: createOutgoingTax
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingTaxInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid decimal value
                  value:
                    status: 422
                    error: 422
                    messages:
                      outgoing_tax_percentage: "The outgoing_tax_percentage field must contain a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/outgoing-taxes/{id}:
    get:
      tags: ["References: Outgoing Taxes"]
      summary: Get single outgoing tax
      description: "Required permission: `all`."
      operationId: getOutgoingTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutgoingTax'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Outgoing Taxes"]
      summary: Update outgoing tax
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOutgoingTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingTaxInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing tax code
                  value:
                    status: 422
                    error: 422
                    messages:
                      outgoing_tax_code: "The outgoing_tax_code field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Outgoing Taxes"]
      summary: Patch outgoing tax
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOutgoingTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingTaxInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be one of: 0, 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Outgoing Taxes"]
      summary: Delete outgoing tax
      description: "Required permission: `administrate`."
      operationId: deleteOutgoingTax
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v3/customer-states:
    get:
      tags: ["References: Customer States"]
      summary: List all customer states
      description: "Required permission: All authenticated users."
      operationId: listCustomerStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Customer States"]
      summary: Create customer state
      description: "Required permission: `administrate`."
      operationId: createCustomerState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_state: "The customer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/customer-states/{id}:
    get:
      tags: ["References: Customer States"]
      summary: Get single customer state
      description: "Required permission: `all`."
      operationId: getCustomerState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Customer States"]
      summary: Update customer state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateCustomerState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_state: "The customer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Customer States"]
      summary: Patch customer state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchCustomerState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      customer_state: "The customer_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Customer States"]
      summary: Delete customer state
      description: "Required permission: `administrate`."
      operationId: deleteCustomerState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Project States
  # ---------------------------------------------------------------------------
  /api/v3/project-states:
    get:
      tags: ["References: Project States"]
      summary: List all project states
      description: "Required permission: All authenticated users."
      operationId: listProjectStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Project States"]
      summary: Create project state
      description: "Required permission: `administrate`."
      operationId: createProjectState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      project_state: "The project_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/project-states/{id}:
    get:
      tags: ["References: Project States"]
      summary: Get single project state
      description: "Required permission: `all`."
      operationId: getProjectState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Project States"]
      summary: Update project state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateProjectState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      project_state: "The project_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Project States"]
      summary: Patch project state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchProjectState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      project_state: "The project_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Project States"]
      summary: Delete project state
      description: "Required permission: `administrate`."
      operationId: deleteProjectState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # Open Task States
  # ---------------------------------------------------------------------------
  /api/v3/open-task-states:
    get:
      tags: ["References: Open Task States"]
      summary: List all open task states
      description: "Required permission: All authenticated users."
      operationId: listOpenTaskStates
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskStateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Open Task States"]
      summary: Create open task state
      description: "Required permission: `administrate`."
      operationId: createOpenTaskState
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskStateInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_state: "The open_task_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/open-task-states/{id}:
    get:
      tags: ["References: Open Task States"]
      summary: Get single open task state
      description: "Required permission: `all`."
      operationId: getOpenTaskState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Open Task States"]
      summary: Update open task state
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOpenTaskState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_state: "The open_task_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Open Task States"]
      summary: Patch open task state
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOpenTaskState
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskStateInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_state: "The open_task_state field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Open Task States"]
      summary: Delete open task state
      description: "Required permission: `administrate`. Deletion is rejected if the state is still referenced by open tasks."
      operationId: deleteOpenTaskState
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  # ---------------------------------------------------------------------------
  # Open Task Categories
  # ---------------------------------------------------------------------------
  /api/v3/open-task-categories:
    get:
      tags: ["References: Open Task Categories"]
      summary: List all open task categories
      description: "Required permission: All authenticated users."
      operationId: listOpenTaskCategories
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskCategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Open Task Categories"]
      summary: Create open task category
      description: "Required permission: `administrate`."
      operationId: createOpenTaskCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskCategoryInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_category: "The open_task_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/open-task-categories/{id}:
    get:
      tags: ["References: Open Task Categories"]
      summary: Get single open task category
      description: "Required permission: `all`."
      operationId: getOpenTaskCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenTaskCategory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Open Task Categories"]
      summary: Update open task category
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateOpenTaskCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_category: "The open_task_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Open Task Categories"]
      summary: Patch open task category
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchOpenTaskCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenTaskCategoryInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Missing required field
                  value:
                    status: 422
                    error: 422
                    messages:
                      open_task_category: "The open_task_category field is required."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Open Task Categories"]
      summary: Delete open task category
      description: "Required permission: `administrate`. Deletion is rejected if the category is still referenced by open tasks."
      operationId: deleteOpenTaskCategory
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  # ---------------------------------------------------------------------------
  # Public Holidays
  # ---------------------------------------------------------------------------
  /api/v3/public-holidays:
    get:
      tags: ["References: Public Holidays"]
      summary: List public holidays
      description: "Required permission: `all`. By default, only entries from today onward are returned."
      operationId: listPublicHolidays
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
        - name: date_from
          in: query
          description: "Optional lower date bound (`YYYY-MM-DD`). If omitted together with `date_to`, default starts at today."
          schema:
            type: string
            format: date
            example: "2026-01-01"
        - name: date_to
          in: query
          description: "Optional upper date bound (`YYYY-MM-DD`)."
          schema:
            type: string
            format: date
            example: "2026-12-31"
        - name: year
          in: query
          description: "Optional calendar year filter (`YYYY`)."
          schema:
            type: integer
            example: 2026
        - name: place_id
          in: query
          description: "Optional place filter."
          schema:
            type: integer
            example: 2
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicHolidayListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Public Holidays"]
      summary: Create public holiday
      description: "Required permission: `administrate`."
      operationId: createPublicHoliday
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicHolidayInput'
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      public_holiday: "The public_holiday field must be a valid date."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/ServerError'

  /api/v3/public-holidays/{id}:
    get:
      tags: ["References: Public Holidays"]
      summary: Get single public holiday
      description: "Required permission: `all`."
      operationId: getPublicHoliday
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicHoliday'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Public Holidays"]
      summary: Update public holiday
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updatePublicHoliday
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicHolidayInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      public_holiday: "The public_holiday field must be a valid date."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Public Holidays"]
      summary: Patch public holiday
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchPublicHoliday
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicHolidayInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                validationError:
                  summary: Invalid date format
                  value:
                    status: 422
                    error: 422
                    messages:
                      public_holiday: "The public_holiday field must be a valid date."
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Public Holidays"]
      summary: Delete public holiday
      description: "Required permission: `administrate`."
      operationId: deletePublicHoliday
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # References: Expenses
  # ---------------------------------------------------------------------------
  /api/v3/expenses:
    get:
      tags: ["References: Expenses"]
      summary: List all expense definitions
      description: "Required permission: All authenticated users."
      operationId: listExpenses
      parameters:
        - $ref: '#/components/parameters/limitParam'
        - $ref: '#/components/parameters/offsetParam'
        - $ref: '#/components/parameters/pageParam'
        - $ref: '#/components/parameters/orderByParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpenseListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags: ["References: Expenses"]
      summary: Create expense definition
      description: "Required permission: `administrate`."
      operationId: createExpense
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpenseInput'
            examples:
              basic:
                summary: New expense definition
                value:
                  expenses: "Fahrtkosten"
                  unit: "km"
                  multiplier: 0.70
                  multiplier_external: 0.70
                  active: 1
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missingExpenses:
                  summary: Missing label
                  value:
                    status: 422
                    error: 422
                    messages:
                      expenses: "The expenses field is required."
                missingUnit:
                  summary: Missing unit
                  value:
                    status: 422
                    error: 422
                    messages:
                      unit: "The unit field is required."
                invalidMultiplier:
                  summary: Invalid multiplier
                  value:
                    status: 422
                    error: 422
                    messages:
                      multiplier: "The multiplier field must be a decimal number."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v3/expenses/{id}:
    get:
      tags: ["References: Expenses"]
      summary: Get single expense definition
      description: "Required permission: `all`."
      operationId: getExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Expense'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: ["References: Expenses"]
      summary: Update expense definition
      description: "Also available via PATCH. Required permission: `administrate`."
      operationId: updateExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpenseInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidMultiplier:
                  summary: Invalid multiplier
                  value:
                    status: 422
                    error: 422
                    messages:
                      multiplier: "The multiplier field must be a decimal number."
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be 0 or 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: ["References: Expenses"]
      summary: Patch expense definition
      description: "Also available via PUT. Required permission: `administrate`."
      operationId: patchExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpenseInput'
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MutationResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalidMultiplier:
                  summary: Invalid multiplier
                  value:
                    status: 422
                    error: 422
                    messages:
                      multiplier: "The multiplier field must be a decimal number."
                invalidActive:
                  summary: Invalid active value
                  value:
                    status: 422
                    error: 422
                    messages:
                      active: "The active field must be 0 or 1."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: ["References: Expenses"]
      summary: Delete expense definition
      description: |
        **Required permission:** `administrate`.

        Returns `409 Conflict` if the expense definition is still referenced by existing expense rows.
      operationId: deleteExpense
      parameters:
        - $ref: '#/components/parameters/idParam'
      responses:
        '200':
          description: Successfully deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Cannot delete – expense definition is still in use
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                error: 409
                messages:
                  error: "Conflict – resource is still referenced and cannot be deleted."


# =============================================================================
# COMPONENTS
# =============================================================================
components:

  # ---------------------------------------------------------------------------
  # Security Schemes
  # ---------------------------------------------------------------------------
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT from `POST /api/jwt`. Tenant is determined from the `tenant` claim in the token — **`X-Tenant` is not required**. If `X-Tenant` is sent, it must match the token's `tenant` claim.
    TenantHeader:
      type: apiKey
      in: header
      name: X-Tenant
      description: |
        **Required for API key authentication** (together with `X-API-Key`). Not required when using only a JWT — the JWT carries the tenant in its claims.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key for tenant-scoped access. **Required together with** `X-Tenant` on every request — API key authentication is not valid without the tenant header. When using only an API key, do not send `Authorization: Bearer`.

  # ---------------------------------------------------------------------------
  # Parameters
  # ---------------------------------------------------------------------------
  parameters:
    idParam:
      name: id
      in: path
      required: true
      description: Unique resource ID
      schema:
        type: integer
        example: 42

    limitParam:
      name: limit
      in: query
      description: "Maximum number of results per page (default: 100, maximum: 1000)"
      schema:
        type: integer
        default: 100
        minimum: 1
        maximum: 1000
        example: 50

    offsetParam:
      name: offset
      in: query
      description: Number of results to skip
      schema:
        type: integer
        minimum: 0
        example: 0

    pageParam:
      name: page
      in: query
      description: Page number (alternative to offset, automatically calculates offset as limit * page)
      schema:
        type: integer
        minimum: 0
        example: 0

    orderByParam:
      name: order_by
      in: query
      description: |
        Sort order for results. Format: `fieldname ASC|DESC`.
        Multiple sort criteria can be comma-separated.
        Only fields from the respective resource are allowed.
      schema:
        type: string
        example: "lastname ASC,firstname ASC"

    searchParam:
      name: search
      in: query
      description: Full-text search across searchable fields of the resource (OR-linked with LIKE)
      schema:
        type: string
        example: "Muster"

    AssistantDocumentType:
      name: type
      in: path
      required: true
      description: Document type
      schema:
        type: string
        enum: [offer, order, invoice]

    AssistantDocumentId:
      name: id
      in: path
      required: true
      description: Document ID
      schema:
        type: integer

  # ---------------------------------------------------------------------------
  # Responses
  # ---------------------------------------------------------------------------
  responses:
    BadRequest:
      description: Malformed request or missing required confirm/query_type
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Unauthorized:
      description: Not authenticated - JWT missing/invalid/expired, or (for API key) missing/invalid `X-Tenant` or `X-API-Key`
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 401
            error: 401
            messages:
              error: "Unauthorized"

    Forbidden:
      description: No permission for this action
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 403
            error: 403
            messages:
              error: "Forbidden"

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 404
            error: 404
            messages:
              error: "Not Found"

    Conflict:
      description: Request conflicts with the current state of the resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 409
            error: 409
            messages:
              error: "Conflict"

    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'

    ServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 500
            error: 500
            messages:
              error: "Internal Server Error"

  # ---------------------------------------------------------------------------
  # Schemas
  # ---------------------------------------------------------------------------
  schemas:

    # -------------------------------------------------------------------------
    # Common Schemas
    # -------------------------------------------------------------------------
    Meta:
      type: object
      description: Pagination metadata for list queries
      properties:
        total:
          type: integer
          description: Total number of records
          example: 150
        limit:
          type: integer
          description: Applied limit
          example: 100
        offset:
          type: integer
          description: Applied offset
          example: 0

    Error:
      type: object
      description: Generic error response.
      properties:
        status:
          type: integer
        error:
          type: integer
        messages:
          type: object
          properties:
            error:
              type: string

    AssistantInfoQueryResult:
      type: object
      properties:
        query_type: { type: string }
        params:
          type: object
          additionalProperties: true
        result:
          type: object
          additionalProperties: true

    AssistantDocumentCreateInput:
      type: object
      required: [document_type, customer_id, articles]
      properties:
        document_type:
          type: string
          enum: [offer, order, invoice]
        customer_id: { type: integer }
        contact_id: { type: integer }
        project_id: { type: integer }
        offer: { type: string }
        order: { type: string }
        invoice: { type: string }
        date: { type: string, format: date }
        articles:
          type: array
          minItems: 1
          items:
            type: object
            additionalProperties: true

    AssistantDocumentResponse:
      type: object
      properties:
        document_type: { type: string }
        id: { type: integer }
        status: { type: string }
        finalized: { type: boolean }
        message: { type: string }
        data:
          type: object
          additionalProperties: true

    ValidationError:
      type: object
      description: |
        Field-level validation error. `messages` is a key/value map where each key is the
        failing field name and the value is the human-readable error message.
      properties:
        status:
          type: integer
          example: 422
        error:
          type: integer
          example: 422
        messages:
          type: object
          additionalProperties:
            type: string

    MutationResponse:
      type: object
      description: Response on successful creation or update
      properties:
        id:
          type: integer
          description: ID of the created/updated record
          example: 42

    DeleteResponse:
      type: object
      description: Response returned after a successful delete operation.
      properties:
        id:
          type: integer
          description: ID of the deleted record
          example: 42

    # -------------------------------------------------------------------------
    # Authentication
    # -------------------------------------------------------------------------
    LoginRequest:
      type: object
      required:
        - tenant
        - username
        - password
      properties:
        tenant:
          type: string
          description: Tenant name (database)
          example: "kunde_ag"
        username:
          type: string
          description: Username
          example: "max.muster"
        password:
          type: string
          format: password
          description: Password
          example: "geheim123"
        two_factor_code:
          type: string
          description: 6-digit TOTP code from the authenticator app (required when 2FA is enabled)
          pattern: '^\d{6}$'
          example: "482916"

    LoginResponse:
      type: object
      properties:
        access_token:
          type: string
          description: JWT Bearer token for subsequent API calls
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

    # -------------------------------------------------------------------------
    # Relations (eingebettete Objekte in Responses)
    # -------------------------------------------------------------------------
    SalutationRelation:
      type: object
      description: Embedded salutation object
      properties:
        salutation_id:
          type: integer
          example: 1
        salutation:
          type: string
          example: "Mr."

    CountryRelation:
      type: object
      description: Embedded country object
      properties:
        country_id:
          type: integer
          example: 1
        country_code:
          type: string
          example: "CH"
        country:
          type: string
          example: "Switzerland"

    CantonRelation:
      type: object
      description: Embedded canton object
      properties:
        canton_id:
          type: integer
          example: 1
        canton_short:
          type: string
          example: "ZH"
        canton:
          type: string
          example: "Zurich"

    CustomerStateRelation:
      type: object
      description: Embedded customer state object
      properties:
        customer_state_id:
          type: integer
          example: 1
        customer_state:
          type: string
          example: "Active"
        color:
          type: string
          example: "#00ff00"

    LanguageRelation:
      type: object
      description: Embedded language object
      properties:
        language_id:
          type: integer
          example: 1
        language:
          type: string
          example: "Deutsch"

    CustomerRegisterRelation:
      type: object
      description: Embedded customer register (from customer_register via customer_register_link)
      properties:
        customer_register_id:
          type: integer
          example: 1
        customer_register:
          type: string
          description: Register label
          example: "Hauptregister"
        sort:
          type: integer
          example: 0
        is_public:
          type: integer
          description: "Public visibility (0 = no, 1 = yes)"
          example: 1
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1

    # -------------------------------------------------------------------------
    # Customer
    # -------------------------------------------------------------------------
    Customer:
      type: object
      description: |
        Customer with all visible fields.
        Searchable fields: company, firstname, lastname, email, email2, phone, mobile_phone, city, zip, external_number, customer_display.
      example:
        customer_id: 42
        is_company: 1
        company: "Muster AG"
        firstname: "Max"
        lastname: "Muster"
        phone_prefix: "CH"
        phone: "044 123 45 67"
        email: "max.muster@example.org"
        city: "Zuerich"
        active: 1
      properties:
        customer_id:
          type: integer
          readOnly: true
          example: 1
        is_company:
          type: integer
          description: "Is company (0 = no, 1 = yes)"
          example: 1
        company:
          type: string
          example: "Muster AG"
        salutation_id:
          type: integer
          example: 1
        salutation_line:
          type: string
          example: "Dear Mr."
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        addition:
          type: string
          example: "Purchasing Department"
        addition2:
          type: string
        customer_state_id:
          type: integer
          example: 1
        attention_of:
          type: string
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1
        street:
          type: string
          example: "Main Street 1"
        zip:
          type: string
          example: "8000"
        city:
          type: string
          example: "Zurich"
        country_id:
          type: integer
          example: 1
        canton_id:
          type: integer
          example: 1
        birthday:
          type: string
          format: date
          example: "1990-01-15"
        phone_prefix:
          type: string
          example: "CH"
        phone:
          type: string
          example: "044 123 45 67"
        phone2_prefix:
          type: string
        phone2:
          type: string
        mobile_phone_prefix:
          type: string
          example: "CH"
        mobile_phone:
          type: string
          example: "079 123 45 67"
        mobile_phone2_prefix:
          type: string
        mobile_phone2:
          type: string
        email:
          type: string
          format: email
          example: "max@muster.ch"
        email2:
          type: string
          format: email
        newsletter:
          type: integer
          description: "Newsletter subscription (0 = no, 1 = yes)"
        website:
          type: string
          example: "https://www.muster.ch"
        external_number:
          type: string
          description: External customer number
        reduction:
          type: number
          description: Discount in percent
          example: 5.0
        customer_payment_terms:
          type: string
        customer_tax_id:
          type: string
          description: Customer tax identifier
        vat_id:
          type: string
          description: Customer VAT identification number (USt-IdNr.)
          maxLength: 50
          example: "CHE-123.456.789 MWST"
        has_email_invoicing:
          type: integer
          description: "Email invoicing (0 = no, 1 = yes)"
        sector_id:
          type: integer
        language_id:
          type: integer
          example: 1
        comment:
          type: string
        is_invoice_address:
          type: integer
          description: "Has separate invoice address (0 = no, 1 = yes)"
        invoice_name:
          type: string
        invoice_addition:
          type: string
        invoice_street:
          type: string
        invoice_zip:
          type: string
        invoice_city:
          type: string
        invoice_country_id:
          type: integer
        is_delivery_address:
          type: integer
          description: "Has separate delivery address (0 = no, 1 = yes)"
        delivery_name:
          type: string
        delivery_addition:
          type: string
        delivery_street:
          type: string
        delivery_zip:
          type: string
        delivery_city:
          type: string
        delivery_country_id:
          type: integer
        date:
          type: string
          format: date
          description: Customer date (from)
        date_until:
          type: string
          format: date
          description: Customer date (until)
        invoice_contact_id:
          type: integer
        invoice_contact_email:
          type: string
          format: email
        custom_input_1:
          type: string
        custom_input_2:
          type: string
        custom_input_3:
          type: string
        custom_input_4:
          type: string
        custom_input_5:
          type: string
        custom_input_6:
          type: string
        custom_input_7:
          type: string
        custom_input_8:
          type: string
        custom_input_9:
          type: string
        custom_input_10:
          type: string
        custom_input_11:
          type: string
        custom_input_12:
          type: string
        custom_input_13:
          type: string
        custom_input_14:
          type: string
        custom_input_15:
          type: string
        custom_text_1:
          type: string
        custom_text_2:
          type: string
        custom_text_3:
          type: string
        custom_text_4:
          type: string
        custom_text_5:
          type: string
        custom_text_6:
          type: string
        custom_text_7:
          type: string
        custom_text_8:
          type: string
        custom_text_9:
          type: string
        custom_text_10:
          type: string
        custom_text_11:
          type: string
        custom_text_12:
          type: string
        custom_text_13:
          type: string
        custom_text_14:
          type: string
        custom_text_15:
          type: string
        custom_select_1:
          type: string
        custom_select_2:
          type: string
        custom_select_3:
          type: string
        custom_select_4:
          type: string
        custom_select_5:
          type: string
        custom_select_6:
          type: string
        custom_select_7:
          type: string
        custom_select_8:
          type: string
        custom_select_9:
          type: string
        custom_select_10:
          type: string
        custom_select_11:
          type: string
        custom_select_12:
          type: string
        custom_select_13:
          type: string
        custom_select_14:
          type: string
        custom_select_15:
          type: string
        location_lat:
          type: number
          format: double
          description: Latitude
          example: 47.3769
        location_lng:
          type: number
          format: double
          description: Longitude
          example: 8.5417
        has_customer_login:
          type: integer
          description: "Has customer login (0 = no, 1 = yes)"
        customer_display:
          type: string
          readOnly: true
          description: Computed display name
          example: "Muster AG / Max Muster"
        salutation:
          $ref: '#/components/schemas/SalutationRelation'
        country:
          $ref: '#/components/schemas/CountryRelation'
        canton:
          $ref: '#/components/schemas/CantonRelation'
        customer_state:
          $ref: '#/components/schemas/CustomerStateRelation'
        language:
          $ref: '#/components/schemas/LanguageRelation'
        customer_registers:
          type: array
          description: Registers linked to this customer (list and detail; empty if none)
          items:
            $ref: '#/components/schemas/CustomerRegisterRelation'

    CustomerInput:
      type: object
      description: Input data for creating/updating a customer
      example:
        is_company: 1
        company: "Muster AG"
        firstname: "Max"
        lastname: "Muster"
        street: "Bahnhofstrasse 1"
        zip: "8000"
        city: "Zuerich"
        country_id: 41
        canton_id: 1
        language_id: 1
        email: "max.muster@example.org"
        mobile_phone: "+41791234567"
        newsletter: 1
        active: 1
      properties:
        is_company:
          type: integer
        company:
          type: string
        salutation_id:
          type: integer
        salutation_line:
          type: string
        firstname:
          type: string
        lastname:
          type: string
        addition:
          type: string
        addition2:
          type: string
        customer_state_id:
          type: integer
        attention_of:
          type: string
        active:
          type: integer
        street:
          type: string
        zip:
          type: string
        city:
          type: string
        country_id:
          type: integer
        canton_id:
          type: integer
        birthday:
          type: string
          format: date
        phone_prefix:
          type: string
        phone:
          type: string
        phone2_prefix:
          type: string
        phone2:
          type: string
        mobile_phone_prefix:
          type: string
        mobile_phone:
          type: string
        mobile_phone2_prefix:
          type: string
        mobile_phone2:
          type: string
        email:
          type: string
          format: email
        email2:
          type: string
          format: email
        newsletter:
          type: integer
        website:
          type: string
        external_number:
          type: string
        reduction:
          type: number
        customer_payment_terms:
          type: string
        customer_tax_id:
          type: string
        vat_id:
          type: string
          maxLength: 50
          example: "CHE-123.456.789 MWST"
        has_email_invoicing:
          type: integer
        sector_id:
          type: integer
        language_id:
          type: integer
        comment:
          type: string
        is_invoice_address:
          type: integer
        invoice_name:
          type: string
        invoice_addition:
          type: string
        invoice_street:
          type: string
        invoice_zip:
          type: string
        invoice_city:
          type: string
        invoice_country_id:
          type: integer
        is_delivery_address:
          type: integer
        delivery_name:
          type: string
        delivery_addition:
          type: string
        delivery_street:
          type: string
        delivery_zip:
          type: string
        delivery_city:
          type: string
        delivery_country_id:
          type: integer
        date:
          type: string
          format: date
        date_until:
          type: string
          format: date
        invoice_contact_id:
          type: integer
        invoice_contact_email:
          type: string
          format: email
        custom_input_1:
          type: string
        custom_input_2:
          type: string
        custom_input_3:
          type: string
        custom_input_4:
          type: string
        custom_input_5:
          type: string
        custom_input_6:
          type: string
        custom_input_7:
          type: string
        custom_input_8:
          type: string
        custom_input_9:
          type: string
        custom_input_10:
          type: string
        custom_input_11:
          type: string
        custom_input_12:
          type: string
        custom_input_13:
          type: string
        custom_input_14:
          type: string
        custom_input_15:
          type: string
        custom_text_1:
          type: string
        custom_text_2:
          type: string
        custom_text_3:
          type: string
        custom_text_4:
          type: string
        custom_text_5:
          type: string
        custom_text_6:
          type: string
        custom_text_7:
          type: string
        custom_text_8:
          type: string
        custom_text_9:
          type: string
        custom_text_10:
          type: string
        custom_text_11:
          type: string
        custom_text_12:
          type: string
        custom_text_13:
          type: string
        custom_text_14:
          type: string
        custom_text_15:
          type: string
        custom_select_1:
          type: string
        custom_select_2:
          type: string
        custom_select_3:
          type: string
        custom_select_4:
          type: string
        custom_select_5:
          type: string
        custom_select_6:
          type: string
        custom_select_7:
          type: string
        custom_select_8:
          type: string
        custom_select_9:
          type: string
        custom_select_10:
          type: string
        custom_select_11:
          type: string
        custom_select_12:
          type: string
        custom_select_13:
          type: string
        custom_select_14:
          type: string
        custom_select_15:
          type: string
        location_lat:
          type: number
          format: double
        location_lng:
          type: number
          format: double
        has_customer_login:
          type: integer

    CustomerCreateInput:
      allOf:
        - $ref: '#/components/schemas/CustomerInput'
        - type: object
          anyOf:
            - required: [company]
            - required: [lastname]
          description: For create requests, at least one of `company` or `lastname` is required.

    CustomerListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Customer'

    # -------------------------------------------------------------------------
    # Contact
    # -------------------------------------------------------------------------
    Contact:
      type: object
      description: |
        Contact with all visible fields.
        Searchable fields: company, firstname, lastname, addition, street, zip, city, email1, email2, website, comment.
      example:
        contact_id: 100
        is_company: 0
        firstname: "Anna"
        lastname: "Beispiel"
        mobile_phone_prefix: "CH"
        mobile_phone: "079 555 44 33"
        email1: "anna.beispiel@example.org"
        city: "Bern"
      properties:
        contact_id:
          type: integer
          readOnly: true
          example: 1
        company:
          type: string
          example: "Muster AG"
        salutation_id:
          type: integer
          example: 1
        salutation_line:
          type: string
          example: "Dear Mr."
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        addition:
          type: string
        addition2:
          type: string
        title:
          type: string
        street:
          type: string
        zip:
          type: string
        city:
          type: string
        country_id:
          type: string
          description: Country ID as stored in contact table
        canton_id:
          type: integer
        birthday:
          type: string
          format: date
        phone:
          type: string
        mobile_phone:
          type: string
        fax:
          type: string
        skype:
          type: string
        email1:
          type: string
          format: email
        email2:
          type: string
          format: email
        newsletter:
          type: integer
          description: "Newsletter subscription (0 = no, 1 = yes)"
        website:
          type: string
        language_id:
          type: string
          description: Language ID as stored in contact table
        comment:
          type: string
        is_company:
          type: integer
          description: "Is company (0 = no, 1 = yes)"
        color:
          type: string
        contact_type_id:
          type: integer
        date:
          type: string
          format: date-time
        create_date:
          type: string
          format: date-time
        modify_date:
          type: string
          format: date-time
        user_id_owner:
          type: integer
        custom_input_1:
          type: string
        custom_input_2:
          type: string
        custom_input_3:
          type: string
        custom_input_4:
          type: string
        custom_input_5:
          type: string
        salutation:
          $ref: '#/components/schemas/SalutationRelation'
        country:
          $ref: '#/components/schemas/CountryRelation'
        canton:
          $ref: '#/components/schemas/CantonRelation'
        language:
          $ref: '#/components/schemas/LanguageRelation'

    ContactInput:
      type: object
      description: Input data for creating/updating a contact
      example:
        salutation_id: 1
        is_company: 0
        firstname: "Erika"
        lastname: "Muster"
        street: "Musterweg 5"
        zip: "8005"
        city: "Zuerich"
        country_id: 41
        canton_id: 1
        language_id: 1
        email1: "erika.muster@example.org"
        mobile_phone: "+41795554433"
        newsletter: 1
        website: "https://example.org"
      properties:
        company:
          type: string
        salutation_id:
          type: integer
        salutation_line:
          type: string
        firstname:
          type: string
        lastname:
          type: string
        addition:
          type: string
        addition2:
          type: string
        title:
          type: string
        street:
          type: string
        zip:
          type: string
        city:
          type: string
        country_id:
          type: string
        canton_id:
          type: integer
        birthday:
          type: string
          format: date
        phone:
          type: string
        mobile_phone:
          type: string
        fax:
          type: string
        skype:
          type: string
        email1:
          type: string
          format: email
        email2:
          type: string
          format: email
        newsletter:
          type: integer
        website:
          type: string
        language_id:
          type: string
        comment:
          type: string
        is_company:
          type: integer
        color:
          type: string
        contact_type_id:
          type: integer
        date:
          type: string
          format: date-time
        create_date:
          type: string
          format: date-time
        modify_date:
          type: string
          format: date-time
        user_id_owner:
          type: integer
        custom_input_1:
          type: string
        custom_input_2:
          type: string
        custom_input_3:
          type: string
        custom_input_4:
          type: string
        custom_input_5:
          type: string

    ContactCreateInput:
      allOf:
        - $ref: '#/components/schemas/ContactInput'
        - type: object
          anyOf:
            - required: [company]
            - required: [lastname]
          description: For create requests, at least one of `company` or `lastname` is required.

    ContactListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Contact'

    # -------------------------------------------------------------------------
    # Customer contact (junction)
    # -------------------------------------------------------------------------
    CustomerContact:
      type: object
      description: |
        Assignment of a contact to a customer with role (function) and comment.
        Searchable fields: function, comment.
      example:
        customer_contact_id: 7
        customer_id: 42
        contact_id: 100
        function: "Contact person"
        comment: "Primary contact"
      properties:
        customer_contact_id:
          type: integer
          readOnly: true
          example: 1
        customer_id:
          type: integer
          example: 42
        contact_id:
          type: integer
          example: 100
        function:
          type: string
          description: Role or function label for this link
          example: "Ansprechpartner Buchhaltung"
        comment:
          type: string
          description: Internal comment for this assignment
        customer:
          $ref: '#/components/schemas/CustomerRelation'
        contact:
          $ref: '#/components/schemas/BusinessContactRelation'

    CustomerContactInput:
      type: object
      description: Input for creating or updating a customer–contact link
      example:
        customer_id: 42
        contact_id: 100
        function: "Contact person"
        comment: "Primary contact"
      properties:
        customer_id:
          type: integer
          description: Required on create
        contact_id:
          type: integer
          description: Required on create
        function:
          type: string
        comment:
          type: string

    CustomerContactListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/CustomerContact'

    # -------------------------------------------------------------------------
    # Project
    # -------------------------------------------------------------------------
    Project:
      type: object
      description: |
        Project with all visible fields.
        Searchable fields: project_id_external, project, comment.
      example:
        project_id: 501
        project: "Website Relaunch"
        project_id_external: "PRJ-2026-001"
        customer_id: 42
        project_state_id: 1
        responsible_user_id: 5
        budget_amount: 25000
        active: 1
      properties:
        project_id:
          type: integer
          readOnly: true
          example: 1
        project_type_id:
          type: integer
        project_id_external:
          type: string
          example: "PRJ-2026-001"
        customer_id:
          type: integer
        place_id:
          type: integer
        currency_id:
          type: integer
        user_id:
          type: integer
        responsible_user_id:
          type: integer
        source_id:
          type: integer
        project_group_id:
          type: integer
        project_state_id:
          type: integer
        project:
          type: string
          example: "Website Relaunch"
        comment:
          type: string
        date_offer_on:
          type: string
          format: date
        date_start:
          type: string
          format: date-time
        date_end:
          type: string
          format: date-time
        project_priority:
          type: integer
        approximated_effort:
          type: number
          format: double
        budget_amount:
          type: number
          format: double
        budget_hours:
          type: integer
        internal_comments:
          type: string
        default_order_header:
          type: string
          description: Default header text applied when creating orders for this project.
        default_order_content:
          type: string
          description: Default content text applied when creating orders for this project.
        default_order_footer:
          type: string
          description: Default footer text applied when creating orders for this project.
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
        restricted_for_role:
          type: integer
        restricted_for_role_data:
          type: string
        color:
          type: string
        custom_amount_for_work_article:
          type: number
          format: double
        project_budgeting_status_id:
          type: integer
        project_budgeting_only_article_category_id:
          type: integer
        latest_invoicing_date:
          type: string
          format: date
        user_id_owner:
          type: integer
        project_has_warranty:
          type: integer
        project_warranty_date_fat:
          type: string
          format: date
        project_warranty_date_sat:
          type: string
          format: date
        project_warranty_duration_in_months:
          type: integer
        customer:
          $ref: '#/components/schemas/BusinessCustomerRelation'
        project_state:
          $ref: '#/components/schemas/ProjectState'

    ProjectInput:
      type: object
      description: Input data for creating/updating a project
      example:
        project: "Sanierung Musterstrasse"
        customer_id: 42
        project_state_id: 1
        project_street: "Musterstrasse 10"
        project_zip: "8000"
        project_city: "Zuerich"
        currency_id: 1
        active: 1
      properties:
        project_type_id:
          type: integer
        project_id_external:
          type: string
        customer_id:
          type: integer
        place_id:
          type: integer
        currency_id:
          type: integer
        user_id:
          type: integer
        responsible_user_id:
          type: integer
        source_id:
          type: integer
        project_group_id:
          type: integer
        project_state_id:
          type: integer
        project:
          type: string
        comment:
          type: string
        date_offer_on:
          type: string
          format: date
        date_start:
          type: string
          format: date-time
        date_end:
          type: string
          format: date-time
        project_priority:
          type: integer
        approximated_effort:
          type: number
          format: double
        budget_amount:
          type: number
          format: double
        budget_hours:
          type: integer
        internal_comments:
          type: string
        default_order_header:
          type: string
        default_order_content:
          type: string
        default_order_footer:
          type: string
        active:
          type: integer
        restricted_for_role:
          type: integer
        restricted_for_role_data:
          type: string
        color:
          type: string
        custom_amount_for_work_article:
          type: number
          format: double
        project_budgeting_status_id:
          type: integer
        project_budgeting_only_article_category_id:
          type: integer
        latest_invoicing_date:
          type: string
          format: date
        user_id_owner:
          type: integer
        project_has_warranty:
          type: integer
        project_warranty_date_fat:
          type: string
          format: date
        project_warranty_date_sat:
          type: string
          format: date
        project_warranty_duration_in_months:
          type: integer

    ProjectCreateInput:
      allOf:
        - $ref: '#/components/schemas/ProjectInput'
        - type: object
          required:
            - customer_id
            - project
          description: For create requests, `customer_id` and `project` are required.

    ProjectListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Project'


    # -------------------------------------------------------------------------
    # Outgoing
    # -------------------------------------------------------------------------
    Outgoing:
      type: object
      description: Outgoing expense row with scalar fields.
      example:
        outgoing_id: 88
        outgoing: "Büromaterial Q2"
        customer_id: 42
        distributor_id: 7
        project_id: 15
        state_id: 1
        date: "2026-06-15"
        end_price: 245.50
        tax_price: 19.80
        currency_id: 1
      properties:
        outgoing_id:
          type: integer
          readOnly: true
          example: 88
        customer_id:
          type: integer
          example: 42
        project_id:
          type: integer
          example: 15
        order_id:
          type: integer
          example: 120
        order_article_id:
          type: integer
          nullable: true
        invoice_article_id:
          type: integer
          nullable: true
        distributor_id:
          type: integer
          example: 7
        place_id:
          type: integer
          example: 1
        state_id:
          type: integer
          example: 1
        outgoing:
          type: string
          maxLength: 255
          example: "Büromaterial Q2"
        outgoing_id_external:
          type: string
          example: "EXT-OUT-88"
        date:
          type: string
          format: date
          example: "2026-06-15"
        index:
          type: string
          description: Indexed PDF text content (read-only via API writes)
          readOnly: true
        currency_id:
          type: integer
          example: 1
        tax_price:
          type: number
          format: double
          example: 19.80
        end_price:
          type: number
          format: double
          example: 245.50
        customer_end_price:
          type: number
          format: double
          nullable: true
        outgoing_tax_id:
          type: integer
          example: 1
        encashment_id:
          type: integer
          example: 2
        effort_id:
          type: integer
          example: 0
        date_pay:
          type: string
          format: date
          example: "2026-07-15"
        user_id:
          type: integer
          readOnly: true
          example: 5
        user_id_owner:
          type: integer
          readOnly: true
          example: 5
        create_date:
          type: string
          format: date
          example: "2026-06-15"
        accounting_export_date_time:
          type: string
          format: date-time
          nullable: true
        payment_accounting_export_date_time:
          type: string
          format: date-time
          nullable: true
        pdf_sha256:
          type: string
          maxLength: 64
          readOnly: true
          description: SHA-256 of the archived receipt PDF (empty if none)
          example: "a3f5c8e9b1d24f7a6c0e5d8b9a1c2e3f4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c"
        pdf_original_filename:
          type: string
          maxLength: 255
          readOnly: true
          example: "beleg-buero.pdf"
        pdf_filesize:
          type: integer
          readOnly: true
          example: 248832
        pdf_uploaded_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
        pdf_uploaded_by_user_id:
          type: integer
          readOnly: true
          example: 5
        comments:
          type: string
          example: "Lieferung per Post"

    OutgoingDetail:
      allOf:
        - $ref: '#/components/schemas/Outgoing'
        - type: object
          properties:
            distributor:
              type: object
              properties:
                distributor_id: { type: integer, example: 7 }
                company: { type: string, example: "Papeterie Zürich AG" }
                city: { type: string, example: "Zürich" }
                email: { type: string, example: "bestellung@papeterie.ch" }
                active: { type: integer, example: 1 }
            customer:
              $ref: '#/components/schemas/BusinessCustomerRelation'
            project:
              type: object
              properties:
                project_id: { type: integer, example: 15 }
                project: { type: string, example: "Sanierung Musterstrasse" }
                customer_id: { type: integer, example: 42 }
                active: { type: integer, example: 1 }
            state:
              $ref: '#/components/schemas/OutgoingState'
            outgoing_tax:
              $ref: '#/components/schemas/OutgoingTax'
            currency:
              type: object
              properties:
                currency_id: { type: integer, example: 1 }
                currency: { type: string, example: "CHF" }
                symbol: { type: string, example: "Fr." }
                conversion_rate: { type: number, example: 1 }
            encashment:
              type: object
              properties:
                encashment_id: { type: integer, example: 2 }
                encashment: { type: string, example: "Rechnung" }
                accounting_account: { type: string, example: "1020" }

    OutgoingInput:
      type: object
      description: Input data for creating/updating an outgoing
      example:
        outgoing: "Büromaterial Q2"
        customer_id: 42
        distributor_id: 7
        project_id: 15
        state_id: 1
        date: "2026-06-15"
        date_pay: "2026-07-15"
        end_price: 245.50
        tax_price: 19.80
        currency_id: 1
        outgoing_tax_id: 1
        encashment_id: 2
        comments: "Lieferung per Post"
      properties:
        customer_id:
          type: integer
        project_id:
          type: integer
        order_id:
          type: integer
        order_article_id:
          type: integer
        invoice_article_id:
          type: integer
        distributor_id:
          type: integer
        place_id:
          type: integer
        state_id:
          type: integer
        outgoing:
          type: string
          maxLength: 255
        outgoing_id_external:
          type: string
        date:
          type: string
          format: date
        currency_id:
          type: integer
        tax_price:
          type: number
          format: double
        end_price:
          type: number
          format: double
        customer_end_price:
          type: number
          format: double
        outgoing_tax_id:
          type: integer
        encashment_id:
          type: integer
        effort_id:
          type: integer
        date_pay:
          type: string
          format: date
        comments:
          type: string

    OutgoingCreateInput:
      allOf:
        - $ref: '#/components/schemas/OutgoingInput'
        - type: object
          required:
            - outgoing
            - customer_id
            - distributor_id
            - end_price
          description: For create requests, `outgoing`, `customer_id`, `distributor_id`, and `end_price` are required.

    OutgoingListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Outgoing'


    OutgoingPdf:
      type: object
      description: Metadata for the immutable archived outgoing receipt PDF (no file payload).
      example:
        outgoing_id: 88
        pdf_sha256: "a3f5c8e9b1d24f7a6c0e5d8b9a1c2e3f4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c"
        pdf_original_filename: "beleg-buero.pdf"
        pdf_filesize: 248832
        pdf_uploaded_at: "2026-07-22 10:15:00"
        pdf_uploaded_by_user_id: 5
        filetype: "application/pdf"
      properties:
        outgoing_id:
          type: integer
          readOnly: true
          example: 88
        pdf_sha256:
          type: string
          maxLength: 64
          example: "a3f5c8e9b1d24f7a6c0e5d8b9a1c2e3f4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c"
        pdf_original_filename:
          type: string
          example: "beleg-buero.pdf"
        pdf_filesize:
          type: integer
          example: 248832
        pdf_uploaded_at:
          type: string
          format: date-time
          nullable: true
        pdf_uploaded_by_user_id:
          type: integer
          example: 5
        filetype:
          type: string
          example: "application/pdf"

    OutgoingPdfUploadInput:
      type: object
      required: [document_name, filetype, content_base64]
      description: |
        JSON Base64 upload. Maximum raw file size is 2 MB.
        Allowed MIME type: application/pdf. Single immutable archive per outgoing.
      example:
        document_name: "beleg-buero.pdf"
        filetype: "application/pdf"
        content_base64: "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL1BhZ2U+PgplbmRvYmoK"
      properties:
        document_name:
          type: string
          maxLength: 255
          description: Original filename; must end with `.pdf`
        pdf_original_filename:
          type: string
          maxLength: 255
          description: Alias for `document_name`
        filetype:
          type: string
          enum: [application/pdf]
        content_base64:
          type: string
          description: Base64-encoded PDF bytes (optionally as a data-URI)

    OutgoingPdfContentBase64:
      type: object
      properties:
        outgoing_id:
          type: integer
          example: 88
        pdf_original_filename:
          type: string
          example: "beleg-buero.pdf"
        filetype:
          type: string
          example: "application/pdf"
        pdf_filesize:
          type: integer
          example: 248832
        content_base64:
          type: string
          example: "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL1BhZ2U+PgplbmRvYmoK"

    # -------------------------------------------------------------------------
    # Distributor
    # -------------------------------------------------------------------------
    Distributor:
      type: object
      description: Distributor/supplier master data.
      example:
        distributor_id: 7
        company: "Papeterie Zürich AG"
        city: "Zürich"
        zip: "8000"
        email: "bestellung@papeterie.ch"
        active: 1
        country_id: 1
      properties:
        distributor_id:
          type: integer
          readOnly: true
          example: 7
        company:
          type: string
          maxLength: 250
          example: "Papeterie Zürich AG"
        addition:
          type: string
          example: "Filiale Oerlikon"
        active:
          type: integer
          enum: [0, 1]
          example: 1
        street:
          type: string
          example: "Bahnhofstrasse 12"
        zip:
          type: string
          example: "8000"
        city:
          type: string
          example: "Zürich"
        country_id:
          type: integer
          example: 1
        canton_id:
          type: integer
          example: 1
        phone:
          type: string
          example: "+41 44 123 45 67"
        mobile_phone:
          type: string
        fax:
          type: string
        email:
          type: string
          format: email
          example: "bestellung@papeterie.ch"
        newsletter:
          type: integer
          enum: [0, 1]
          example: 0
        website:
          type: string
          example: "https://www.papeterie.ch"
        external_number:
          type: string
          example: "D-1042"
        language_id:
          type: string
          example: "de"
        comment:
          type: string
        credit_limit:
          type: string
        payment_target:
          type: string
          example: "30"
        distributor_customer_id:
          type: string
          example: "K-9981"
        date:
          type: string
          format: date
          example: "2024-01-10"
        user_id_owner:
          type: integer
          readOnly: true
          example: 5
        country:
          type: object
          properties:
            country_id: { type: integer, example: 1 }
            country_code: { type: string, example: "CH" }
            country: { type: string, example: "Switzerland" }
        canton:
          type: object
          properties:
            canton_id: { type: integer, example: 1 }
            canton_short: { type: string, example: "ZH" }
            canton: { type: string, example: "Zürich" }

    DistributorInput:
      type: object
      description: Input data for creating/updating a distributor.
      example:
        company: "Papeterie Zürich AG"
        street: "Bahnhofstrasse 12"
        zip: "8000"
        city: "Zürich"
        country_id: 1
        email: "bestellung@papeterie.ch"
        active: 1
      properties:
        company:
          type: string
          maxLength: 250
        addition:
          type: string
        active:
          type: integer
          enum: [0, 1]
        street:
          type: string
        zip:
          type: string
        city:
          type: string
        country_id:
          type: integer
        canton_id:
          type: integer
        phone:
          type: string
        mobile_phone:
          type: string
        fax:
          type: string
        email:
          type: string
          format: email
        newsletter:
          type: integer
          enum: [0, 1]
        website:
          type: string
        external_number:
          type: string
        language_id:
          type: string
        comment:
          type: string
        credit_limit:
          type: string
        payment_target:
          type: string
        distributor_customer_id:
          type: string
        date:
          type: string
          format: date

    DistributorCreateInput:
      allOf:
        - $ref: '#/components/schemas/DistributorInput'
        - type: object
          required:
            - company
          description: For create requests, `company` is required.

    DistributorListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Distributor'

    # -------------------------------------------------------------------------
    # Article
    # -------------------------------------------------------------------------
    Article:
      type: object
      description: |
        Article with all visible fields.
        Searchable fields: article, description, original_number, ean_code.
      example:
        article_id: 12
        article: "Arbeitsstunde"
        article_tax_id: 1
        article_unit_id: 1
        article_price: 145.00
        active: 1
      properties:
        article_id:
          type: integer
          readOnly: true
          example: 1
        article_category_id:
          type: integer
        article_sub_category_id:
          type: integer
        article_tax_id:
          type: integer
        article_unit_id:
          type: integer
        article:
          type: string
          maxLength: 50
        original_number:
          type: string
        description:
          type: string
        article_manufacturer_id:
          type: integer
        article_location_id:
          type: integer
        article_weight:
          type: number
        ean_code:
          type: string
        article_warranty_id:
          type: integer
        article_manufacturer_link:
          type: string
        manufacturer_link:
          type: string
        expiration_date:
          type: number
        picture_url:
          type: string
        article_price:
          type: number
        is_tax_included:
          type: integer
          description: "Tax included (0 = no, 1 = yes)"
        purchase_price:
          type: number
        accounting_account:
          type: string
        accounting_cost_centre:
          type: string
        rapport:
          type: integer
          description: "Available in time report (0 = no, 1 = yes)"
        rapport_factor:
          type: number
        rapport_special:
          type: string
        order_budget_hours:
          type: number
        article_id_external:
          type: integer
        article_external_type:
          type: string
        has_stock:
          type: integer
          description: "Stock enabled (0 = no, 1 = yes)"
        minimal_stock:
          type: integer
        remind_no_stock:
          type: integer
          description: "Remind if no stock (0 = no, 1 = yes)"
        remind_minimal_stock:
          type: integer
          description: "Remind at minimal stock (0 = no, 1 = yes)"
        create_date:
          type: string
          format: date
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
        sort:
          type: integer
        article_category:
          $ref: '#/components/schemas/ArticleCategory'
        article_tax:
          $ref: '#/components/schemas/ArticleTax'
        article_unit:
          $ref: '#/components/schemas/ArticleUnit'

    ArticleInput:
      type: object
      description: Input data for creating/updating an article
      example:
        article: "Analyseleistung"
        article_tax_id: 1
        article_unit_id: 1
        article_price: 145
        active: 1
      properties:
        article_category_id:
          type: integer
        article_sub_category_id:
          type: integer
        article_tax_id:
          type: integer
        article_unit_id:
          type: integer
        article:
          type: string
        original_number:
          type: string
        description:
          type: string
        article_manufacturer_id:
          type: integer
        article_location_id:
          type: integer
        article_weight:
          type: number
        ean_code:
          type: string
        article_warranty_id:
          type: integer
        article_manufacturer_link:
          type: string
        manufacturer_link:
          type: string
        expiration_date:
          type: number
        picture_url:
          type: string
        article_price:
          type: number
        is_tax_included:
          type: integer
        purchase_price:
          type: number
        accounting_account:
          type: string
        accounting_cost_centre:
          type: string
        rapport:
          type: integer
        rapport_factor:
          type: number
        rapport_special:
          type: string
        order_budget_hours:
          type: number
        article_id_external:
          type: integer
        article_external_type:
          type: string
        has_stock:
          type: integer
        minimal_stock:
          type: integer
        remind_no_stock:
          type: integer
        remind_minimal_stock:
          type: integer
        create_date:
          type: string
          format: date
        owner_user_id:
          type: integer
        active:
          type: integer
        sort:
          type: integer

    ArticleCreateInput:
      allOf:
        - $ref: '#/components/schemas/ArticleInput'
        - type: object
          required:
            - article_tax_id
            - article_unit_id
            - article
          description: For create requests, `article_tax_id`, `article_unit_id`, and `article` are required.

    ArticleListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Article'

    # -------------------------------------------------------------------------
    # Settings
    # -------------------------------------------------------------------------
    SettingsResponse:
      type: object
      description: Non-sensitive tenant settings returned by GET /api/v3/settings.
      required: [data]
      properties:
        data:
          type: object
          additionalProperties: true
          description: Setting key to value map. Keys matching sensitive patterns are excluded.
          example:
            using_places: true
            round_chf: false
            customer_base_number: "1000"

    # -------------------------------------------------------------------------
    # User
    # -------------------------------------------------------------------------
    MyUserProfile:
      type: object
      description: Own user profile returned by GET /api/v3/my-user.
      example:
        user_id: 5
        firstname: "Luca"
        lastname: "Muster"
        username: "luca.muster"
        email: "luca.muster@example.org"
        language: "de"
      properties:
        user_id:
          type: integer
          readOnly: true
          example: 5
        firstname:
          type: string
          example: "Luca"
        lastname:
          type: string
          example: "Muster"
        username:
          type: string
          example: "luca.muster"
        email:
          type: string
          format: email
          example: "luca.muster@example.org"
        language:
          type: string
          example: "de"

    MyUserProfileInput:
      type: object
      description: |
        Self-service profile update for PUT/PATCH /api/v3/my-user.
        All fields are optional on PATCH; PUT accepts partial updates as well (same semantics as PATCH).
      properties:
        firstname:
          type: string
          example: "Luca"
        lastname:
          type: string
          example: "Muster"
        username:
          type: string
          example: "luca.muster"
        email:
          type: string
          format: email
          example: "luca.muster@example.org"
        language:
          type: string
          example: "de"
        password:
          type: string
          format: password
          writeOnly: true
          description: Plaintext password; stored using the legacy hash scheme.

    MyUserProfileMutationResponse:
      type: object
      description: Response on successful update of own profile via /api/v3/my-user.
      properties:
        data:
          $ref: '#/components/schemas/MyUserProfile'
        message:
          type: string
          example: "Resource updated successfully"

    UserLocationInput:
      type: object
      required:
        - latitude
        - longitude
        - recorded_at
      properties:
        latitude:
          type: number
          format: double
          minimum: -90
          maximum: 90
          example: 47.376886
        longitude:
          type: number
          format: double
          minimum: -180
          maximum: 180
          example: 8.541694
        accuracy:
          type: number
          format: double
          nullable: true
          minimum: 0
          description: GPS accuracy in meters
          example: 12.5
        recorded_at:
          type: string
          format: date-time
          description: Capture time in ISO 8601 format
          example: "2026-06-22T14:30:00.000Z"

    UserLocation:
      type: object
      properties:
        user_location_id:
          type: integer
          example: 15
        user_id:
          type: integer
          example: 5
        latitude:
          type: number
          format: double
          example: 47.376886
        longitude:
          type: number
          format: double
          example: 8.541694
        accuracy:
          type: number
          format: double
          nullable: true
          example: 12.5
        recorded_at:
          type: string
          format: date-time
          example: "2026-06-22T14:30:00+00:00"
        created_at:
          type: string
          format: date-time
          example: "2026-06-22T14:30:01+00:00"

    UserLocationMutationResponse:
      type: object
      description: Response on successful creation via /api/v3/user-location.
      properties:
        id:
          type: integer
          example: 15
        status:
          type: string
          example: success
        data:
          $ref: '#/components/schemas/UserLocation'
        message:
          type: string
          example: "Resource created successfully"

    User:
      type: object
      description: |
        User with all visible fields.
        Searchable fields: firstname, lastname, email, phone, mobile_phone, zip, city.
      example:
        user_id: 5
        firstname: "Luca"
        lastname: "Muster"
        username: "luca.muster"
        email: "luca.muster@example.org"
        language: "de"
        active: 1
      properties:
        user_id:
          type: integer
          readOnly: true
          example: 1
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        email:
          type: string
          format: email
          example: "max@muster.ch"
        address:
          type: string
          example: "Main Street 1, 8000 Zurich"
        zip:
          type: string
          example: "8000"
        city:
          type: string
          example: "Zürich"
        phone:
          type: string
          example: "044 123 45 67"
        mobile_phone:
          type: string
          example: "079 123 45 67"
        birthdate:
          type: string
          format: date
          example: "1990-01-15"
        function:
          type: string
          description: Function / job title
          example: "Project Manager"
        price_per_hour:
          type: number
          description: Hourly rate (external)
          example: 150.00
        price_per_hour_internal:
          type: number
          description: Hourly rate (internal)
          example: 120.00
        language:
          type: string
          description: User language
          example: "de"
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1
        default_place_id:
          type: integer
          description: Default place reference (`place.place_id`)
        is_management_member:
          type: integer
          enum: [0, 1]
          description: Executive management member (0 = no, 1 = yes)
        has_holiday_scheduling:
          type: integer
          enum: [0, 1]
          description: Ferien-/Absenzplanung aktiv
        has_order_scheduling:
          type: integer
          enum: [0, 1]
          description: Auftragsplanung aktiv
        has_attendance:
          type: integer
          enum: [0, 1]
          description: Zeiterfassung/Anwesenheit aktiv
        has_pikett:
          type: integer
          enum: [0, 1]
          description: Pikett aktiv
        two_factor_enabled:
          type: boolean
          description: Whether two-factor authentication is enabled for this user
          readOnly: true
          example: false

    UserInput:
      type: object
      description: Input data for creating/updating a user
      example:
        firstname: "Anna"
        lastname: "Beispiel"
        email: "anna.beispiel@example.org"
        address: "Hauptstrasse 7"
        zip: "8001"
        city: "Zuerich"
        language: "de"
        active: 1
      properties:
        firstname:
          type: string
        lastname:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
        zip:
          type: string
        city:
          type: string
        phone:
          type: string
        mobile_phone:
          type: string
        birthdate:
          type: string
          format: date
        function:
          type: string
        price_per_hour:
          type: number
        price_per_hour_internal:
          type: number
        language:
          type: string
        active:
          type: integer
        default_place_id:
          type: integer

    UserCreateInput:
      allOf:
        - $ref: '#/components/schemas/UserInput'
        - type: object
          required:
            - firstname
            - lastname
            - email
          description: For create requests, `firstname`, `lastname`, and `email` are required.

    UserListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'

    UserAbsenceUserRelation:
      type: object
      description: Embedded user (subset) for absence entries
      properties:
        user_id:
          type: integer
        firstname:
          type: string
        lastname:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
        zip:
          type: string
        city:
          type: string
        phone:
          type: string
        mobile_phone:
          type: string
        birthdate:
          type: string
          format: date
        function:
          type: string
        price_per_hour:
          type: number
        price_per_hour_internal:
          type: number
        language:
          type: string
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"

    UserAbsence:
      type: object
      description: Absence entry (table `user_holiday_scheduling`)
      properties:
        user_holiday_scheduling_id:
          type: integer
        date:
          type: string
          format: date
        date_to:
          type: string
          format: date
        only_half_day:
          type: integer
          description: "Half day (0 = no, 1 = yes)"
        user_id:
          type: integer
        absence:
          type: string
          description: Absence type code (e.g. holiday)
          maxLength: 25
        comments:
          type: string
        absence_percentage:
          type: integer
          minimum: 0
          maximum: 100
        user:
          $ref: '#/components/schemas/UserAbsenceUserRelation'

    UserAbsenceInput:
      type: object
      description: Input for creating or updating a user absence
      example:
        user_id: 5
        date: "2026-06-01"
        date_to: "2026-06-05"
        only_half_day: 0
        absence: "holiday"
        comments: "Sommerferien"
        absence_percentage: 100
      properties:
        date:
          type: string
          format: date
        date_to:
          type: string
          format: date
        only_half_day:
          type: integer
          enum: [0, 1]
        user_id:
          type: integer
        absence:
          type: string
          maxLength: 25
        comments:
          type: string
        absence_percentage:
          type: integer
          minimum: 0
          maximum: 100

    UserAbsenceListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/UserAbsence'

    # -------------------------------------------------------------------------
    # Support Ticket Relations
    # -------------------------------------------------------------------------
    SupportTicketStateRelation:
      type: object
      description: Embedded ticket state object
      properties:
        support_ticket_state_id:
          type: integer
          example: 1
        support_ticket_state:
          type: string
          example: "Open"
        color:
          type: string
          example: "#ff9900"

    CustomerRelation:
      type: object
      description: Embedded customer object (short form)
      properties:
        customer_id:
          type: integer
          example: 42
        company:
          type: string
          example: "Muster AG"
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        customer_display:
          type: string
          example: "Muster AG / Max Muster"

    SupportTicketCategoryRelation:
      type: object
      description: Embedded ticket category object
      properties:
        support_ticket_category_id:
          type: integer
          example: 3
        support_ticket_category:
          type: string
          example: "Technical Support"

    # -------------------------------------------------------------------------
    # Support Ticket
    # -------------------------------------------------------------------------
    SupportTicket:
      type: object
      description: |
        Support ticket with all visible fields.
        Searchable fields: subject, question, email_from.
      example:
        support_ticket_id: 9001
        customer_id: 42
        contact_id: 100
        subject: "Question about invoice"
        question: "Please explain line item 3"
        support_ticket_state_id: 1
        priority: 2
        is_done: 0
      properties:
        support_ticket_id:
          type: integer
          readOnly: true
          example: 1
        customer_id:
          type: integer
          example: 42
        contact_id:
          type: integer
          description: Associated contact
        project_id:
          type: integer
          description: Associated project
        priority:
          type: integer
          description: "Priority (0 = very low, 1 = low, 2 = normal, 3 = high, 4 = very high)"
          enum: [0, 1, 2, 3, 4]
          default: 2
          example: 2
        support_ticket_state_id:
          type: integer
          example: 1
        email_from:
          type: string
          format: email
          description: Sender email address
          example: "kunde@example.com"
        cc:
          type: string
          description: CC recipients
        bcc:
          type: string
          description: BCC recipients
        subject:
          type: string
          description: Ticket subject
          example: "Printer issue office 3rd floor"
        question:
          type: string
          description: Question / problem description (HTML)
        answer:
          type: string
          description: Answer (HTML)
        answer_user_id:
          type: integer
          description: User ID of the responder
        date_time_incoming:
          type: string
          format: date-time
          description: Incoming timestamp
          example: "2026-02-22T10:30:00"
        date_time_assigned:
          type: string
          format: date-time
          description: Assignment timestamp
        date_time_answered:
          type: string
          format: date-time
          description: Answer timestamp
        date_time_done:
          type: string
          format: date-time
          description: Completion timestamp
        creator_user_id:
          type: integer
          description: Ticket creator user ID
        assigned_user_id:
          type: integer
          description: Assigned handler user ID
        done_until_date:
          type: string
          format: date
          description: Due date
        done_until_time:
          type: string
          description: Due time
          example: "17:00"
        execution_place:
          type: string
          description: Execution location
        execution_date:
          type: string
          format: date
          description: Execution date
        execution_time:
          type: string
          description: Execution time
          example: "09:00"
        execution_time_estimated:
          type: string
          description: Estimated time effort
          example: "2h"
        identification_hash:
          type: string
          description: Unique identification hash (e.g. for email matching)
        is_done:
          type: integer
          description: "Done (0 = no, 1 = yes)"
          example: 0
        send_assigned_confirmation:
          type: integer
          description: "Send assignment confirmation (0 = no, 1 = yes)"
        send_done_confirmation:
          type: integer
          description: "Send completion confirmation (0 = no, 1 = yes)"
        notify_enclosed:
          type: integer
          description: "Notify enclosed (0 = no, 1 = yes)"
        parent_support_ticket_id:
          type: integer
          description: ID of the parent ticket (0 = root ticket)
          example: 0
        support_ticket_category_id:
          type: integer
          example: 3
        support_ticket_state:
          $ref: '#/components/schemas/SupportTicketStateRelation'
        customer:
          $ref: '#/components/schemas/CustomerRelation'
        support_ticket_category:
          $ref: '#/components/schemas/SupportTicketCategoryRelation'

    SupportTicketWithHistory:
      description: Support ticket with embedded history
      allOf:
        - $ref: '#/components/schemas/SupportTicket'
        - type: object
          properties:
            history:
              type: array
              description: Chronological history of follow-up tickets (questions and answers)
              items:
                $ref: '#/components/schemas/SupportTicket'

    SupportTicketInput:
      type: object
      description: |
        Input data for creating/updating a support ticket.
        Optional FK fields are existence-validated when provided.
        On create, `creator_user_id` is auto-filled from JWT (`sub`) when omitted.
        If `assigned_user_id` is provided, `date_time_assigned` is auto-set to current timestamp.
      example:
        customer_id: 42
        contact_id: 100
        subject: "Login does not work"
        question: "User cannot sign in."
        priority: 2
        support_ticket_state_id: 1
        assigned_user_id: 5
      properties:
        customer_id:
          type: integer
        contact_id:
          type: integer
        project_id:
          type: integer
        order_id:
          type: integer
        order_category_id:
          type: integer
        priority:
          type: integer
          description: "Allowed values: 0..4; defaults to 2 on create when omitted"
          enum: [0, 1, 2, 3, 4]
          default: 2
        support_ticket_state_id:
          type: integer
        email_from:
          type: string
          format: email
        cc:
          type: string
        bcc:
          type: string
        subject:
          type: string
        question:
          type: string
        answer:
          type: string
        answer_user_id:
          type: integer
        date_time_incoming:
          type: string
          format: date-time
        date_time_assigned:
          type: string
          format: date-time
        date_time_answered:
          type: string
          format: date-time
        date_time_done:
          type: string
          format: date-time
        creator_user_id:
          type: integer
          description: Auto-filled from JWT (`sub`) on create when omitted
        assigned_user_id:
          type: integer
          description: When provided, `date_time_assigned` is auto-set to current timestamp
        done_until_date:
          type: string
          format: date
        done_until_time:
          type: string
        execution_place:
          type: string
        execution_date:
          type: string
          format: date
        execution_time:
          type: string
        execution_time_estimated:
          type: string
        is_done:
          type: integer
        send_assigned_confirmation:
          type: integer
        send_done_confirmation:
          type: integer
        notify_enclosed:
          type: integer
        parent_support_ticket_id:
          type: integer
          description: Set to 0 for root tickets, or parent ticket ID for follow-ups
        identification_hash:
          type: string
        support_ticket_category_id:
          type: integer

    SupportTicketCreateInput:
      allOf:
        - $ref: '#/components/schemas/SupportTicketInput'
        - type: object
          required:
            - customer_id
            - email_from
          description: For create requests, `customer_id` and `email_from` are required.

    SupportTicketAssignToUserInput:
      type: object
      required: [assigned_user_id]
      properties:
        assigned_user_id:
          type: integer
          example: 5

    SupportTicketAnswerInput:
      type: object
      required: [answer]
      properties:
        answer:
          type: string
          example: "Please clear the browser cache and try again."
        answer_user_id:
          type: integer
          description: Optional; if omitted, resolved from JWT (`sub`)
          example: 5

    SupportTicketActionResponse:
      type: object
      properties:
        id:
          type: integer
          example: 123
        status:
          type: string
          example: "success"
        data:
          $ref: '#/components/schemas/SupportTicket'
        message:
          type: string
          example: "Ticket action executed successfully"

    SupportTicketListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          description: List of root tickets, each with an embedded history array
          items:
            $ref: '#/components/schemas/SupportTicketWithHistory'

    # -------------------------------------------------------------------------
    # Comment
    # -------------------------------------------------------------------------
    Comment:
      type: object
      description: Comment linked to a business resource (invoice, offer, order, support ticket, customer, contact, or project).
      properties:
        comment_id:
          type: integer
          readOnly: true
          example: 16526
        link_id:
          type: integer
          example: 5102
        link_type:
          type: string
          enum: [invoice, offer, order, support_ticket, customer, contact, project]
          example: "offer"
        comment:
          type: string
          example: "Please check briefly."
        creator_user_id:
          type: integer
          example: 1
        create_date_time:
          type: string
          format: date-time
          example: "2025-04-18 14:28:37"
        assigned_user_id:
          type: string
          description: Legacy string field (single or comma-separated user IDs).
          example: "118"
        notify_by:
          type: string
          example: "notify_by_slack;notify_by_pushover"
        creator_user:
          type: object
          nullable: true
          properties:
            user_id:
              type: integer
            firstname:
              type: string
            lastname:
              type: string
            email:
              type: string

    CommentInput:
      type: object
      required:
        - link_id
        - link_type
        - comment
      properties:
        link_id:
          type: integer
          minimum: 1
        link_type:
          type: string
          enum: [invoice, offer, order, support_ticket, customer, contact, project]
        comment:
          type: string
          minLength: 1
        creator_user_id:
          type: integer
          minimum: 1
          description: Optional for JWT requests; auto-resolved from JWT when omitted.
        create_date_time:
          type: string
          format: date-time
          description: Optional. Defaults to the current server datetime when omitted (or empty).
        assigned_user_id:
          type: string
          description: Optional. Defaults to the JWT user id when omitted (or empty) and the request is JWT-authenticated.
        notify_by:
          type: string

    CommentListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Comment'

    # -------------------------------------------------------------------------
    # Salutation
    # -------------------------------------------------------------------------
    Salutation:
      type: object
      description: Reference value for salutations (e.g. Herr, Frau).
      properties:
        salutation_id:
          type: integer
          readOnly: true
          example: 1
        salutation:
          type: string
          example: "Mr."
        sort:
          type: integer
          example: 1

    SalutationInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        salutation: "Herr"
        sort: 10
      properties:
        salutation:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    SalutationListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Salutation'

    # -------------------------------------------------------------------------
    # Country
    # -------------------------------------------------------------------------
    Country:
      type: object
      description: Reference value for countries.
      properties:
        country_id:
          type: integer
          readOnly: true
          example: 1
        country_code:
          type: string
          example: "CH"
        country:
          type: string
          example: "Switzerland"

    CountryInput:
      type: object
      example:
        country_code: "CH"
        country: "Schweiz"
      properties:
        country_code:
          type: string
        country:
          type: string

    CountryListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Country'

    # -------------------------------------------------------------------------
    # Canton
    # -------------------------------------------------------------------------
    Canton:
      type: object
      description: Reference value for Swiss cantons.
      properties:
        canton_id:
          type: integer
          readOnly: true
          example: 1
        canton_short:
          type: string
          example: "ZH"
        canton:
          type: string
          example: "Zurich"
        sort:
          type: integer
          example: 1

    CantonInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        canton_short: "ZH"
        canton: "Zuerich"
        sort: 10
      properties:
        canton_short:
          type: string
        canton:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    CantonListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Canton'

    # -------------------------------------------------------------------------
    # Currency
    # -------------------------------------------------------------------------
    Currency:
      type: object
      description: Reference value for currencies with conversion rates.
      properties:
        currency_id:
          type: integer
          readOnly: true
          example: 1
        currency:
          type: string
          example: "CHF"
        symbol:
          type: string
          example: "Fr."
        conversion_rate:
          type: number
          description: Conversion rate
          example: 1.0
        standard:
          type: integer
          description: "Default currency (0 = no, 1 = yes)"
          example: 1

    CurrencyInput:
      type: object
      example:
        currency: "CHF"
        symbol: "CHF"
        conversion_rate: 1
        standard: 1
      properties:
        currency:
          type: string
        symbol:
          type: string
        conversion_rate:
          type: number
        standard:
          type: integer

    CurrencyListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Currency'

    # -------------------------------------------------------------------------
    # Department
    # -------------------------------------------------------------------------
    Department:
      type: object
      description: Reference value for internal departments.
      properties:
        department_id:
          type: integer
          readOnly: true
          example: 1
        department:
          type: string
          example: "Development"
        sort:
          type: integer
          example: 1

    DepartmentInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        department: "Service"
        sort: 10
      properties:
        department:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    DepartmentListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Department'

    # -------------------------------------------------------------------------
    # Place
    # -------------------------------------------------------------------------
    Place:
      type: object
      description: Reference value for locations or work sites.
      properties:
        place_id:
          type: integer
          readOnly: true
          example: 1
        place:
          type: string
          example: "Head office"
        street:
          type: string
          example: "Bahnhofstrasse 1"
        zip:
          type: string
          example: "8001"
        city:
          type: string
          example: "Zürich"
        location_lat:
          type: number
          format: double
          description: Latitude (defaults to 0 if omitted on create)
        location_lng:
          type: number
          format: double
          description: Longitude (defaults to 0 if omitted on create)
        sort:
          type: integer
          example: 1
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1

    PlaceInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value. `location_lat`/`location_lng` default to 0 if omitted."
      example:
        place: "Werkhof Zuerich"
        street: "Industriestrasse 20"
        zip: "8050"
        city: "Zuerich"
        active: 1
      properties:
        place:
          type: string
        street:
          type: string
        zip:
          type: string
        city:
          type: string
        location_lat:
          type: number
          format: double
        location_lng:
          type: number
          format: double
        sort:
          type: integer
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"

    PlaceListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Place'

    # -------------------------------------------------------------------------
    # Encashment
    # -------------------------------------------------------------------------
    Encashment:
      type: object
      description: Reference value for payment terms (e.g. 30 days net).
      properties:
        encashment_id:
          type: integer
          readOnly: true
          example: 1
        encashment:
          type: string
          example: "30 days net"
        accounting_account:
          type: string
          description: Accounting account
        payment_means_code:
          type: string
          description: "UNCL 4461 payment means code (e.g. 30 = credit transfer, 58 = SEPA credit transfer)"
          maxLength: 10
          example: "30"
        accounting_encashment_key:
          type: string
          description: Accounting encashment key
        sort:
          type: integer
          example: 1

    EncashmentInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        encashment: "Bankueberweisung"
        accounting_account: "1020"
        payment_means_code: "30"
        accounting_encashment_key: "BANK"
        sort: 10
      properties:
        encashment:
          type: string
        accounting_account:
          type: string
        payment_means_code:
          type: string
          maxLength: 10
          example: "30"
        accounting_encashment_key:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    EncashmentListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Encashment'

    # -------------------------------------------------------------------------
    # Language
    # -------------------------------------------------------------------------
    Language:
      type: object
      description: Reference value for languages.
      properties:
        language_id:
          type: integer
          readOnly: true
          example: 1
        language:
          type: string
          example: "Deutsch"
        sort:
          type: integer
          example: 1

    LanguageInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        language: "Deutsch"
        sort: 10
      properties:
        language:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    LanguageListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Language'

    # -------------------------------------------------------------------------
    # Absence Type
    # -------------------------------------------------------------------------
    AbsenceType:
      type: object
      description: Reference value for user absence types (e.g. vacation, sick leave).
      properties:
        absence_type_id:
          type: integer
          readOnly: true
          example: 1
        absence_type:
          type: string
          example: "Vacation"
        sort:
          type: integer
          example: 1
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1

    AbsenceTypeInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        absence_type: "Ferien"
        active: 1
        sort: 10
      properties:
        absence_type:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."
        active:
          type: integer

    AbsenceTypeListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/AbsenceType'

    # -------------------------------------------------------------------------
    # Article Category
    # -------------------------------------------------------------------------
    ArticleCategory:
      type: object
      description: Reference value for article categories.
      properties:
        article_category_id:
          type: integer
          readOnly: true
          example: 1
        article_category:
          type: string
          example: "Services"
        article_category_price_percentage:
          type: number
          description: Category price percentage
        accounting_account:
          type: string
          description: Accounting account
        article_category_external_id:
          type: string
          description: External ID
        article_category_external_type:
          type: string
          description: External type
        parent_article_category_id:
          type: integer
          description: Parent category ID (tree structure)
        sort:
          type: integer
          example: 1

    ArticleCategoryInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        article_category: "Dienstleistung"
        accounting_account: "3200"
        sort: 10
      properties:
        article_category:
          type: string
        article_category_price_percentage:
          type: number
        accounting_account:
          type: string
        article_category_external_id:
          type: string
        article_category_external_type:
          type: string
        parent_article_category_id:
          type: integer
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    ArticleCategoryListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/ArticleCategory'

    # -------------------------------------------------------------------------
    # Article Tax
    # -------------------------------------------------------------------------
    ArticleTax:
      type: object
      description: Reference value for VAT/tax rates applied to articles.
      properties:
        article_tax_id:
          type: integer
          readOnly: true
          example: 1
        article_tax:
          type: string
          example: "MwSt 8.1%"
        article_tax_percentage:
          type: number
          description: Tax rate in percent
          example: 8.1
        article_tax_allocatable:
          type: integer
          description: "Allocatable (0 = no, 1 = yes)"
        article_tax_code:
          type: string
          description: Tax code
        vat_category_code:
          type: string
          description: "EN 16931 VAT category code (e.g. S, Z, E, AE, K, G, O)"
          maxLength: 10
          example: "S"
        vat_exemption_reason:
          type: string
          description: Reason for VAT exemption (relevant for category E)
          example: "Steuerbefreite Ausfuhrlieferung"
        active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1
        sort:
          type: integer
          example: 1

    ArticleTaxInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        article_tax: "MWST 8.1%"
        article_tax_percentage: 8.1
        article_tax_code: "VAT81"
        vat_category_code: "S"
        active: 1
        sort: 10
      properties:
        article_tax:
          type: string
        article_tax_percentage:
          type: number
        article_tax_allocatable:
          type: integer
        article_tax_code:
          type: string
        vat_category_code:
          type: string
          maxLength: 10
          example: "S"
        vat_exemption_reason:
          type: string
          example: "Steuerbefreite Ausfuhrlieferung"
        active:
          type: integer
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    ArticleTaxListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/ArticleTax'

    # -------------------------------------------------------------------------
    # Article Unit
    # -------------------------------------------------------------------------
    ArticleUnit:
      type: object
      description: Reference value for units of measure (e.g. hours, pieces).
      properties:
        article_unit_id:
          type: integer
          readOnly: true
          example: 1
        article_unit:
          type: string
          example: "Hour"
        unece_code:
          type: string
          description: "UNECE Recommendation 20/21 unit code (e.g. HUR = hour, C62 = piece)"
          maxLength: 10
          example: "HUR"
        factor_for_hour:
          type: number
          description: Conversion factor to hours
          example: 1.0
        sort:
          type: integer
          example: 1

    ArticleUnitInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        article_unit: "Std."
        unece_code: "HUR"
        factor_for_hour: 1
        sort: 10
      properties:
        article_unit:
          type: string
        unece_code:
          type: string
          maxLength: 10
          example: "HUR"
        factor_for_hour:
          type: number
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    ArticleUnitListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/ArticleUnit'

    # -------------------------------------------------------------------------
    # Bank Account
    # -------------------------------------------------------------------------
    BankAccount:
      type: object
      description: Reference value for bank accounts used in invoicing (IBAN, QR-IBAN, ESR).
      properties:
        bank_account_id:
          type: integer
          readOnly: true
          example: 1
        bank_account:
          type: string
          description: Bank account name
          example: "Hauptkonto UBS"
        esr_account_number:
          type: string
          description: ESR participant number
        esr_reference_number_base:
          type: string
          description: ESR reference number base
        esr_for:
          type: string
          description: ESR in favor of
        accounting_bank_account:
          type: string
          description: Accounting bank account
        accounting_bank_id:
          type: string
          description: Buchhaltungs-Bank-ID
        iban:
          type: string
          description: IBAN number
          example: "CH93 0076 2011 6238 5295 7"
        bic:
          type: string
          description: BIC/SWIFT code
          maxLength: 11
          example: "POFICHBEXXX"
        qr_iban:
          type: string
          description: QR-IBAN for QR invoices
        qr_ze_name:
          type: string
          description: QR payee name
        qr_ze_strasse:
          type: string
          description: QR payee street
        qr_ze_hausnummer:
          type: string
          description: QR payee house number
        qr_ze_postleitzahl:
          type: string
          description: QR payee postal code
        qr_ze_ort:
          type: string
          description: QR payee city
        qr_ze_land:
          type: string
          description: QR payee country
        sort:
          type: integer
          example: 1

    BankAccountInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        bank_account: "Hauptkonto"
        iban: "CH9300762011623852957"
        bic: "POFICHBEXXX"
        accounting_bank_account: "1020"
        sort: 10
      properties:
        bank_account:
          type: string
        esr_account_number:
          type: string
        esr_reference_number_base:
          type: string
        esr_for:
          type: string
        accounting_bank_account:
          type: string
        accounting_bank_id:
          type: string
        iban:
          type: string
        bic:
          type: string
          maxLength: 11
          example: "POFICHBEXXX"
        qr_iban:
          type: string
        qr_ze_name:
          type: string
        qr_ze_strasse:
          type: string
        qr_ze_hausnummer:
          type: string
        qr_ze_postleitzahl:
          type: string
        qr_ze_ort:
          type: string
        qr_ze_land:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    BankAccountListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/BankAccount'

    # -------------------------------------------------------------------------
    # Email Template
    # -------------------------------------------------------------------------
    EmailTemplate:
      type: object
      description: Reference value for email templates used when sending documents.
      properties:
        email_template_id:
          type: integer
          readOnly: true
          example: 1
        table:
          type: string
          description: Associated table (e.g. invoice, offer, order)
          example: "invoice"
        email_template:
          type: string
          description: Template name
          example: "Invoice Standard"
        email_subject:
          type: string
          description: Email subject (with placeholders)
          example: "Your invoice no. {invoice_number}"
        email_body:
          type: string
          description: Email body (HTML, with placeholders)
        language_id:
          type: integer
          example: 1
        is_active:
          type: integer
          description: "Active (0 = no, 1 = yes)"
          example: 1
        is_default:
          type: integer
          description: "Default template (0 = no, 1 = yes)"
          example: 0
        sort:
          type: integer
          example: 1

    EmailTemplateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        table: "invoice"
        email_template: "Standard invoice"
        email_subject: "Your invoice"
        email_body: "Dear Sir or Madam..."
        language_id: 1
        is_active: 1
        is_default: 0
      properties:
        table:
          type: string
        email_template:
          type: string
        email_subject:
          type: string
        email_body:
          type: string
        language_id:
          type: integer
        is_active:
          type: integer
        is_default:
          type: integer
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    EmailTemplateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/EmailTemplate'

    # -------------------------------------------------------------------------
    # Business Objects (Offer / Order / Invoice)
    # -------------------------------------------------------------------------
    BusinessCustomerRelation:
      type: object
      properties:
        customer_id:
          type: integer
          example: 42
        company:
          type: string
          example: "Muster AG"
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        customer_display:
          type: string
          example: "Muster AG / Max Muster"

    BusinessContactRelation:
      type: object
      properties:
        contact_id:
          type: integer
          example: 100
        company:
          type: string
          example: "Muster AG"
        firstname:
          type: string
          example: "Erika"
        lastname:
          type: string
          example: "Muster"
        email1:
          type: string
          format: email
          example: "erika@muster.ch"
        phone:
          type: string
          example: "044 123 45 67"

    BusinessProjectRelation:
      type: object
      properties:
        project_id:
          type: integer
          example: 77
        project_id_external:
          type: string
          example: "PROJ-2026-001"
        project:
          type: string
          example: "Website Relaunch"

    BusinessOrderCategoryRelation:
      type: object
      properties:
        order_category_id:
          type: integer
          example: 5
        order_category:
          type: string
          example: "Web Development"
        color:
          type: string
          example: "#ff6600"

    BusinessOrderSchedulingCategoryRelation:
      type: object
      properties:
        order_scheduling_category_id:
          type: integer
          example: 3
        order_scheduling_category:
          type: string
          example: "Montage"
        sort:
          type: integer
          example: 1

    OfferStateEmbedded:
      type: object
      properties:
        offer_state_id:
          type: integer
          example: 1
        offer_state:
          type: string
          example: "Open"
        color:
          type: string
          example: "#ff9900"

    OrderStateEmbedded:
      type: object
      properties:
        order_state_id:
          type: integer
          example: 1
        order_state:
          type: string
          example: "Planned"
        color:
          type: string
          example: "#0066cc"

    InvoiceStateEmbedded:
      type: object
      properties:
        invoice_state_id:
          type: integer
          example: 1
        invoice_state:
          type: string
          example: "Open"
        color:
          type: string
          example: "#009933"

    OfferArticle:
      type: object
      additionalProperties: true
      description: Article position linked to an offer.

    OfferHistory:
      type: object
      additionalProperties: true
      description: Row from `offer_history`.

    OrderArticle:
      type: object
      additionalProperties: true
      description: Row from `order_article`.

    OrderHistory:
      type: object
      additionalProperties: true
      description: Row from `order_history`.

    InvoiceArticle:
      type: object
      additionalProperties: true
      description: Row from `invoice_article`.

    InvoiceHistory:
      type: object
      additionalProperties: true
      description: Row from `invoice_history`.

    InvoicePayment:
      type: object
      additionalProperties: true
      description: Row from `invoice_payment`.

    BusinessOrderRelation:
      type: object
      properties:
        order_id:
          type: integer
          example: 1001
        order_id_external:
          type: string
          example: "ORD-2026-001"
        order:
          type: string
          example: "Projektauftrag 2026"
        date:
          type: string
          format: date-time

    BusinessArticleRelation:
      type: object
      properties:
        article_id:
          type: integer
          example: 12
        number:
          type: string
          example: "ART-1000"
        description:
          type: string
          example: "Entwicklung"

    UserRelation:
      type: object
      properties:
        user_id:
          type: integer
          example: 8
        firstname:
          type: string
          example: "Max"
        lastname:
          type: string
          example: "Muster"
        email:
          type: string
          format: email
          example: "max@example.com"

    TaskRelation:
      type: object
      properties:
        task_id:
          type: integer
          example: 5
        task:
          type: string
          example: "Support"
        article_id:
          type: integer
          nullable: true

    RapportExpense:
      $ref: '#/components/schemas/TimeReportExpense'

    TimeReport:
      type: object
      example:
        rapport_id: 9001
        customer_id: 42
        project_id: 77
        order_id: 1001
        user_id: 8
        article_id: 12
        task_id: 5
        date: "2026-04-22"
        minutes: 120
        unit_price: 150
        rapport_factor: 1
      properties:
        rapport_id:
          type: integer
          readOnly: true
          example: 9001
        customer_id:
          type: integer
          nullable: true
        project_id:
          type: integer
          nullable: true
        order_id:
          type: integer
          nullable: true
        order_article_id:
          type: integer
          nullable: true
        support_ticket_id:
          type: integer
          nullable: true
        reference_number:
          type: string
          nullable: true
        article_id:
          type: integer
          nullable: true
        task_id:
          type: integer
          nullable: true
        user_id:
          type: integer
        rapport_tariff_id:
          type: integer
          nullable: true
        minutes:
          type: number
          format: float
          nullable: true
        minutes_allocatable:
          type: integer
          nullable: true
        unit_price:
          type: number
          format: double
          nullable: true
        flatrate_price:
          type: number
          format: double
          nullable: true
        rapport_factor:
          type: number
          format: double
          nullable: true
        description:
          type: string
          nullable: true
        date:
          type: string
          format: date
        time_from:
          type: string
          nullable: true
          example: "08:30:00"
        time_to:
          type: string
          nullable: true
          example: "10:00:00"
        is_invoiced:
          type: integer
          nullable: true
        invoice_id:
          type: integer
          nullable: true

    TimeReportDetail:
      allOf:
        - $ref: '#/components/schemas/TimeReport'
        - type: object
          properties:
            customer:
              $ref: '#/components/schemas/BusinessCustomerRelation'
              nullable: true
            project:
              $ref: '#/components/schemas/BusinessProjectRelation'
              nullable: true
            order:
              $ref: '#/components/schemas/BusinessOrderRelation'
              nullable: true
            user:
              $ref: '#/components/schemas/UserRelation'
              nullable: true
            article:
              $ref: '#/components/schemas/BusinessArticleRelation'
              nullable: true
            task:
              $ref: '#/components/schemas/TaskRelation'
              nullable: true
            rapport_expenses:
              type: array
              items:
                $ref: '#/components/schemas/RapportExpense'

    TimeReportInput:
      type: object
      description: |
        Input for creating/updating a time report.
        Legacy fields `work_package_id` and `order_article_option_id` are intentionally not part of this API contract.
        `date` must be `YYYY-MM-DD`.
        `rapport_factor` defaults to `1` when omitted.
        Effort input:
        - either provide `minutes`
        - or provide both `time_from` and `time_to` (`HH:MM` or `HH:MM:SS`); then `minutes` is calculated automatically.
        `time_to` must be equal to or later than `time_from`.
        Additional write policies are validated at runtime and may return `422`:
        date lock windows, no-future/no-cross-year settings, daily max hours per user,
        max hours per entry (except rapport-special articles), protected/checked rapport edit lock,
        and absence-task restriction on full-day public holidays.
      example:
        customer_id: 1042
        project_id: 3201
        order_id: 54012
        article_id: 701
        task_id: 31
        user_id: 23
        date: "2026-04-15"
        minutes: 90
        description: "Incident analysis and email delivery fix"
      additionalProperties: false
      allOf:
        - oneOf:
            - required: [minutes]
            - required: [time_from, time_to]
      properties:
        customer_id:
          type: integer
          nullable: true
        project_id:
          type: integer
          nullable: true
        order_id:
          type: integer
          nullable: true
        order_article_id:
          type: integer
          nullable: true
        support_ticket_id:
          type: integer
          nullable: true
        reference_number:
          type: string
          nullable: true
        article_id:
          type: integer
          nullable: true
        task_id:
          type: integer
          nullable: true
        user_id:
          type: integer
        rapport_tariff_id:
          type: integer
          nullable: true
        minutes:
          type: number
          format: float
          nullable: true
          description: Optional when `time_from` and `time_to` are provided.
        minutes_allocatable:
          type: integer
          nullable: true
        unit_price:
          type: number
          format: double
          nullable: true
        flatrate_price:
          type: number
          format: double
          nullable: true
        rapport_factor:
          type: number
          format: double
          nullable: true
          default: 1
        description:
          type: string
          nullable: true
        date:
          type: string
          format: date
          description: Accepts `YYYY-MM-DD` (without time).
        time_from:
          type: string
          nullable: true
          description: Requires `time_to` when provided.
          pattern: '^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'
          example: "08:30:00"
        time_to:
          type: string
          nullable: true
          description: Requires `time_from` when provided and must not be earlier than `time_from`.
          pattern: '^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$'
          example: "10:00:00"
        is_invoiced:
          type: integer
          nullable: true
        invoice_id:
          type: integer
          nullable: true

    TimeReportListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/TimeReport'

    TimeReportExpense:
      type: object
      description: Expense position linked to a time report entry.
      properties:
        rapport_expenses_id:
          type: integer
          readOnly: true
          example: 501
        rapport_id:
          type: integer
          example: 9001
        expenses_id:
          type: integer
          nullable: true
          example: 12
        expenses:
          type: string
          example: "Fahrtkosten"
        quantity:
          type: number
          format: double
          example: 1
        multiplier:
          type: number
          format: double
          example: 0.70
        multiplier_external:
          type: number
          format: double
          example: 0.70
        end_price:
          type: number
          format: double
          example: 42.00
        order_article_id:
          type: integer
          nullable: true
        comments:
          type: string
          example: "Travel to customer"

    TimeReportExpenseDetail:
      allOf:
        - $ref: '#/components/schemas/TimeReportExpense'
        - type: object
          properties:
            rapport:
              type: object
              nullable: true
              properties:
                rapport_id:
                  type: integer
                user_id:
                  type: integer
                customer_id:
                  type: integer
                  nullable: true
                project_id:
                  type: integer
                  nullable: true
                order_id:
                  type: integer
                  nullable: true
                date:
                  type: string
                  format: date
                minutes:
                  type: number
                  format: float
                  nullable: true
            expenses:
              type: object
              nullable: true
              properties:
                expenses_id:
                  type: integer
                expenses:
                  type: string
                unit:
                  type: string
                multiplier:
                  type: number
                  format: double
                multiplier_external:
                  type: number
                  format: double
                active:
                  type: integer

    TimeReportExpenseInput:
      type: object
      properties:
        rapport_id:
          type: integer
          description: Required. Must reference an existing time report.
          example: 9001
        expenses_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing expense definition when provided.
          example: 12
        expenses:
          type: string
          maxLength: 255
          example: "Fahrtkosten"
        quantity:
          type: number
          format: double
          example: 1
        multiplier:
          type: number
          format: double
          example: 0.70
        multiplier_external:
          type: number
          format: double
          example: 0.70
        end_price:
          type: number
          format: double
          example: 42.00
        order_article_id:
          type: integer
          nullable: true
        comments:
          type: string
          example: "Travel to customer"

    TimeReportExpenseListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/TimeReportExpense'

    # -------------------------------------------------------------------------
    # Open Tasks
    # -------------------------------------------------------------------------
    OpenTaskItem:
      type: object
      description: |
        Open task with all visible fields and embedded relations.
        Searchable fields: title, description.
      example:
        open_task_id: 1001
        title: "Revise offer"
        customer_id: 42
        project_id: 15
        priority: 3
        date_end: "2026-06-30"
        open_task_state_id: 1
        percentage_finished: 0
      properties:
        open_task_id:
          type: integer
          readOnly: true
          example: 1001
        open_task_state_id:
          type: integer
          nullable: true
          example: 1
        open_task_category_id:
          type: integer
          nullable: true
          example: 2
        title:
          type: string
          example: "Revise offer"
        description:
          type: string
          example: "Adjust line items 3 and 4 as requested by the customer"
        priority:
          type: integer
          description: "Priority level (0 = very low, 1 = low, 2 = normal, 3 = high, 4 = very high)"
          enum: [0, 1, 2, 3, 4]
          default: 2
          example: 3
        private_task:
          type: integer
          description: "Private task flag (0 = no, 1 = yes)"
          enum: [0, 1]
          example: 0
        date_end:
          type: string
          format: date
          description: Due date
          example: "2026-06-30"
        time_end:
          type: string
          nullable: true
          description: Due time (HH:MM or HH:MM:SS)
          example: "17:00"
        execution_time_estimated:
          type: number
          format: double
          description: Estimated effort in hours
          example: 2.5
        percentage_finished:
          type: integer
          minimum: 0
          maximum: 100
          description: Completion percentage
          example: 0
        customer_id:
          type: integer
          example: 42
        project_id:
          type: integer
          nullable: true
          example: 15
        order_id:
          type: integer
          nullable: true
          example: 200
        contact_id:
          type: integer
          nullable: true
          example: 100
        user_id_responsible:
          type: integer
          nullable: true
          description: User responsible for the task
          example: 7
        user_id_owner:
          type: integer
          nullable: true
          description: Task owner (auto-filled from JWT on create when omitted)
          example: 5
        create_date:
          type: string
          format: date-time
          readOnly: true
          description: Creation timestamp (auto-set on create)
          example: "2026-05-20T09:15:00"
        open_task_schedule_id:
          type: integer
          example: 0
        parent_task_id:
          type: integer
          description: Parent task ID (0 = root task)
          example: 0
        date_notify:
          type: string
          format: date
          nullable: true
          description: Notification date
          example: "2026-06-25"
        notify_by:
          type: string
          example: ""
        notify_enclosed:
          type: string
          example: ""
        customer:
          type: object
          nullable: true
          properties:
            customer_id:
              type: integer
            company:
              type: string
              example: "Muster AG"
            firstname:
              type: string
              example: "Hans"
            lastname:
              type: string
              example: "Muster"
            customer_display:
              type: string
              example: "Muster AG"
        contact:
          type: object
          nullable: true
          properties:
            contact_id:
              type: integer
            company:
              type: string
            firstname:
              type: string
            lastname:
              type: string
            email1:
              type: string
              format: email
            phone:
              type: string
        project:
          type: object
          nullable: true
          properties:
            project_id:
              type: integer
            project:
              type: string
              example: "Website Relaunch 2026"
        order:
          type: object
          nullable: true
          properties:
            order_id:
              type: integer
            order:
              type: string
              example: "Wartungsvertrag 2026"
            date:
              type: string
              format: date
        user_responsible:
          type: object
          nullable: true
          properties:
            user_id:
              type: integer
            firstname:
              type: string
            lastname:
              type: string
            email:
              type: string
              format: email
        user_owner:
          type: object
          nullable: true
          properties:
            user_id:
              type: integer
            firstname:
              type: string
            lastname:
              type: string
            email:
              type: string
              format: email
        open_task_state:
          type: object
          nullable: true
          properties:
            open_task_state_id:
              type: integer
            open_task_state:
              type: string
              example: "In Bearbeitung"
            color:
              type: string
              example: "#FFA500"
        open_task_category:
          type: object
          nullable: true
          properties:
            open_task_category_id:
              type: integer
            open_task_category:
              type: string
              example: "Intern"

    OpenTaskInput:
      type: object
      description: |
        Shared input fields for creating and updating an open task.
        All FK fields are existence-validated when provided.
        `priority` defaults to 2 on create when omitted.
        `user_id_owner` is auto-filled from JWT on create when omitted.
      properties:
        title:
          type: string
          maxLength: 255
          example: "Revise offer"
        description:
          type: string
          example: "Adjust line items 3 and 4 as requested by the customer"
        customer_id:
          type: integer
          description: Must reference an existing customer
          example: 42
        project_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing project when provided.
          example: 15
        order_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing order when provided.
          example: 200
        contact_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing contact when provided.
          example: 100
        user_id_responsible:
          type: integer
          nullable: true
          description: Optional. Must reference an existing user when provided.
          example: 7
        user_id_owner:
          type: integer
          nullable: true
          description: Optional. Auto-filled from JWT on create when omitted.
          example: 5
        open_task_state_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing task state when provided.
          example: 1
        open_task_category_id:
          type: integer
          nullable: true
          description: Optional. Must reference an existing task category when provided.
          example: 2
        priority:
          type: integer
          description: "Priority level (0 = very low … 4 = very high). Defaults to 2 on create."
          enum: [0, 1, 2, 3, 4]
          default: 2
          example: 3
        private_task:
          type: integer
          description: "Private task flag (0 = no, 1 = yes)"
          enum: [0, 1]
          example: 0
        date_end:
          type: string
          format: date
          example: "2026-06-30"
        time_end:
          type: string
          description: Due time (HH:MM or HH:MM:SS)
          example: "17:00"
        execution_time_estimated:
          type: number
          format: double
          description: Estimated effort in hours
          example: 2.5
        percentage_finished:
          type: integer
          minimum: 0
          maximum: 100
          example: 0
        open_task_schedule_id:
          type: integer
          example: 0
        parent_task_id:
          type: integer
          description: Parent task ID (0 = root task)
          example: 0
        date_notify:
          type: string
          format: date
          example: "2026-06-25"
        notify_by:
          type: string
          maxLength: 255
          example: ""
        notify_enclosed:
          type: string
          maxLength: 255
          example: ""

    OpenTaskCreateInput:
      allOf:
        - $ref: '#/components/schemas/OpenTaskInput'
        - type: object
          required:
            - title
            - customer_id
          description: For create requests, `title` and `customer_id` are required.

    OpenTaskUpdateInput:
      allOf:
        - $ref: '#/components/schemas/OpenTaskInput'
        - type: object
          description: All fields optional for partial update (PUT and PATCH share identical semantics).

    OpenTaskListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OpenTaskItem'

    Offer:
      type: object
      example:
        offer_id: 3001
        offer_id_external: "OFF-2026-001"
        offer: "Wartungsvertrag 2026"
        customer_id: 42
        state_id: 1
        period: "2026-01"
        amount_netto: 1200.0
        amount_tax: 97.2
        amount_brutto: 1297.2
      properties:
        offer_id:
          type: integer
          readOnly: true
          example: 1
        offer_id_external:
          type: string
          example: "OFF-2026-001"
        offer:
          type: string
          example: "Wartungsvertrag 2026"
        customer_id:
          type: integer
        contact_id:
          type: integer
        project_id:
          type: integer
        order_category_id:
          type: integer
        order_scheduling_category_id:
          type: integer
        state_id:
          type: integer
        date:
          type: string
          format: date-time
        deadline_date:
          type: string
          format: date
        deadline_description:
          type: string
          description: Optional free-text description related to the deadline.
        period:
          type: string
        priority:
          type: integer
        amount_netto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated total from non-optional article rows.
        amount_tax:
          type: number
          format: double
          readOnly: true
          description: Server-calculated tax total from non-optional article rows.
        amount_brutto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated gross total from non-optional article rows.
        user_id:
          type: integer
        user_id_owner:
          type: integer
    OfferDetail:
      allOf:
        - $ref: '#/components/schemas/Offer'
        - type: object
          properties:
            customer:
              $ref: '#/components/schemas/BusinessCustomerRelation'
              nullable: true
            contact:
              $ref: '#/components/schemas/BusinessContactRelation'
              nullable: true
            project:
              $ref: '#/components/schemas/BusinessProjectRelation'
              nullable: true
            order_category:
              $ref: '#/components/schemas/BusinessOrderCategoryRelation'
              nullable: true
            state:
              $ref: '#/components/schemas/OfferStateEmbedded'
              nullable: true
            articles:
              type: array
              items:
                $ref: '#/components/schemas/OfferArticle'
            optional_articles:
              type: array
              items:
                $ref: '#/components/schemas/OfferArticle'
            offer_history:
              type: array
              items:
                $ref: '#/components/schemas/OfferHistory'

    OfferInput:
      type: object
      description: |
        Input data for creating/updating an offer.
        On create, `customer_id` and `articles` are required.
        If `offer` is empty, default label from API config is used.
        If `state_id` is omitted, first state by `sort` is used.
        If `currency_id` is omitted, currency with `standard=1` is used.
        Only documented properties are accepted.
      additionalProperties: false
      example:
        customer_id: 42
        offer: "Renovation quote"
        date: "2026-04-15"
        deadline_date: "2026-04-30"
        currency_id: 1
        state_id: 1
        articles:
          - article_number: "ART-1000"
            quantity: 2
            unit_price: 149.9
      properties:
        offer_id_external:
          type: string
        offer:
          type: string
        customer_id:
          type: integer
        contact_id:
          type: integer
          nullable: true
        project_id:
          type: integer
          nullable: true
        currency_id:
          type: integer
        order_category_id:
          type: integer
        state_id:
          type: integer
        date:
          type: string
          format: date
        deadline_date:
          type: string
          format: date
          nullable: true
        deadline_description:
          type: string
          nullable: true
        period:
          type: string
          nullable: true
        priority:
          type: integer
          nullable: true
        user_id:
          type: integer
          nullable: true
        articles:
          type: array
          description: Required for create. On update, replaces all current document articles when provided.
          items:
            $ref: '#/components/schemas/BusinessDocumentArticleInput'

    OfferCreateInput:
      allOf:
        - $ref: '#/components/schemas/OfferInput'
        - type: object
          required:
            - customer_id
            - articles
          properties:
            articles:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/BusinessDocumentArticleInput'

    OfferListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Offer'

    Order:
      type: object
      example:
        order_id: 4001
        order_id_external: "ORD-2026-001"
        order: "Projektauftrag 2026"
        customer_id: 42
        state_id: 1
        period: "2026-01"
        amount_netto: 5000.0
        amount_tax: 405.0
        amount_brutto: 5405.0
      properties:
        order_id:
          type: integer
          readOnly: true
          example: 1
        order_id_external:
          type: string
          example: "ORD-2026-001"
        order:
          type: string
          example: "Projektauftrag 2026"
        customer_id:
          type: integer
        contact_id:
          type: integer
        project_id:
          type: integer
        order_category_id:
          type: integer
        state_id:
          type: integer
        date:
          type: string
          format: date-time
        deadline_date:
          type: string
          format: date
        deadline_description:
          type: string
          description: Optional free-text description related to the deadline.
        period:
          type: string
        priority:
          type: integer
        amount_netto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated total from non-optional article rows.
        amount_tax:
          type: number
          format: double
          readOnly: true
          description: Server-calculated tax total from non-optional article rows.
        amount_brutto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated gross total from non-optional article rows.
        user_id:
          type: integer
        user_id_owner:
          type: integer
    OrderDetail:
      allOf:
        - $ref: '#/components/schemas/Order'
        - type: object
          properties:
            customer:
              $ref: '#/components/schemas/BusinessCustomerRelation'
              nullable: true
            contact:
              $ref: '#/components/schemas/BusinessContactRelation'
              nullable: true
            project:
              $ref: '#/components/schemas/BusinessProjectRelation'
              nullable: true
            order_category:
              $ref: '#/components/schemas/BusinessOrderCategoryRelation'
              nullable: true
            order_scheduling_category:
              $ref: '#/components/schemas/BusinessOrderSchedulingCategoryRelation'
              nullable: true
            state:
              $ref: '#/components/schemas/OrderStateEmbedded'
              nullable: true
            articles:
              type: array
              items:
                $ref: '#/components/schemas/OrderArticle'
            optional_articles:
              type: array
              items:
                $ref: '#/components/schemas/OrderArticle'
            order_history:
              type: array
              items:
                $ref: '#/components/schemas/OrderHistory'

    OrderInput:
      type: object
      description: |
        Input data for creating/updating an order.
        On create, `customer_id` and `articles` are required.
        If `order` is empty, default label from API config is used.
        If `state_id` is omitted, first state by `sort` is used.
        If `currency_id` is omitted, currency with `standard=1` is used.
        Only documented properties are accepted.
      additionalProperties: false
      example:
        customer_id: 42
        order: "Renovation order"
        date: "2026-04-15"
        deadline_date: "2026-05-10"
        currency_id: 1
        state_id: 1
        articles:
          - article_number: "ART-1000"
            quantity: 3
            unit_price: 149.9
      properties:
        order_id_external:
          type: string
        order:
          type: string
        customer_id:
          type: integer
        contact_id:
          type: integer
          nullable: true
        project_id:
          type: integer
          nullable: true
        currency_id:
          type: integer
        order_category_id:
          type: integer
        order_scheduling_category_id:
          type: integer
          nullable: true
        state_id:
          type: integer
        date:
          type: string
          format: date
        deadline_date:
          type: string
          format: date
          nullable: true
        deadline_description:
          type: string
          nullable: true
        period:
          type: string
          nullable: true
        priority:
          type: integer
          nullable: true
        user_id_project_manager:
          type: integer
          nullable: true
        user_id_worker1:
          type: integer
          nullable: true
        user_id_worker2:
          type: integer
          nullable: true
        user_id:
          type: integer
          nullable: true
        articles:
          type: array
          description: Required for create. On update, replaces all current document articles when provided.
          items:
            $ref: '#/components/schemas/BusinessDocumentArticleInput'

    OrderCreateInput:
      allOf:
        - $ref: '#/components/schemas/OrderInput'
        - type: object
          required:
            - customer_id
            - articles
          properties:
            articles:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/BusinessDocumentArticleInput'

    OrderListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Order'

    Invoice:
      type: object
      example:
        invoice_id: 5001
        invoice_id_external: "INV-2026-001"
        invoice: "Invoice April 2026"
        customer_id: 42
        state_id: 1
        date: "2026-04-15T09:30:00Z"
        amount_netto: 5000.0
        amount_tax: 405.0
        amount_brutto: 5405.0
        is_canceled: 0
      properties:
        invoice_id:
          type: integer
          readOnly: true
          example: 1
        invoice_id_external:
          type: string
          example: "INV-2026-001"
        invoice:
          type: string
          example: "Invoice 2026"
        customer_id:
          type: integer
        contact_id:
          type: integer
        project_id:
          type: integer
        order_category_id:
          type: integer
        state_id:
          type: integer
        date:
          type: string
          format: date-time
        date_print:
          type: string
          format: date
        date_pay:
          type: string
          format: date
          description: Actual payment date. Set on API payment create when empty, or in yourBureau when paid; empty/unset while unpaid.
        period:
          type: string
        deadline_description:
          type: string
          description: Optional free-text description related to the deadline.
        amount_netto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated total from non-optional article rows.
        amount_tax:
          type: number
          format: double
          readOnly: true
          description: Server-calculated tax total from non-optional article rows.
        amount_brutto:
          type: number
          format: double
          readOnly: true
          description: Server-calculated gross total from non-optional article rows.
        user_id:
          type: integer
        user_id_owner:
          type: integer
        is_canceled:
          type: integer
        e_invoice_xml_created_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: Timestamp when the e-invoice archive was created. Empty if no e-invoice has been issued yet.
          example: "2026-07-15T11:17:00Z"
        e_invoice_profile:
          type: string
          readOnly: true
          description: Profile used when the e-invoice was archived (e.g. en16931). Empty if not yet archived.
          example: "en16931"
    InvoiceDetail:
      allOf:
        - $ref: '#/components/schemas/Invoice'
        - type: object
          properties:
            customer:
              $ref: '#/components/schemas/BusinessCustomerRelation'
              nullable: true
            contact:
              $ref: '#/components/schemas/BusinessContactRelation'
              nullable: true
            project:
              $ref: '#/components/schemas/BusinessProjectRelation'
              nullable: true
            order_category:
              $ref: '#/components/schemas/BusinessOrderCategoryRelation'
              nullable: true
            state:
              $ref: '#/components/schemas/InvoiceStateEmbedded'
              nullable: true
            articles:
              type: array
              items:
                $ref: '#/components/schemas/InvoiceArticle'
            invoice_history:
              type: array
              items:
                $ref: '#/components/schemas/InvoiceHistory'
            invoice_payments:
              type: array
              items:
                $ref: '#/components/schemas/InvoicePayment'

    InvoiceInput:
      type: object
      description: |
        Input data for creating/updating an invoice.
        On create, `customer_id` and `articles` are required.
        If `invoice` is empty, default label from API config is used.
        If `state_id` is omitted, first state by `sort` is used.
        If `encashment_id` is omitted, default encashment is used.
        If `currency_id` is omitted, currency with `standard=1` is used.
        `date_pay` is not accepted on create/update; it is set on payment
        (`POST /api/v3/invoices-payment`) or in yourBureau when the invoice is paid.
        Only documented properties are accepted.
      additionalProperties: false
      example:
        customer_id: 42
        invoice: "Invoice April 2026"
        date: "2026-04-15"
        payment_terms: "30"
        currency_id: 1
        state_id: 1
        encashment_id: 1
        articles:
          - article_number: "ART-1000"
            quantity: 2
            unit_price: 149.9
      properties:
        invoice_id_external:
          type: string
        invoice:
          type: string
        customer_id:
          type: integer
        contact_id:
          type: integer
          nullable: true
        project_id:
          type: integer
          nullable: true
        from_request_id:
          type: integer
          nullable: true
        from_offer_id:
          type: integer
          nullable: true
        from_order_id:
          type: integer
          nullable: true
        from_aconto_order_id:
          type: integer
          nullable: true
        currency_id:
          type: integer
        encashment_id:
          type: integer
          nullable: true
        order_category_id:
          type: integer
        state_id:
          type: integer
        date:
          type: string
          format: date
        date_print:
          type: string
          format: date
          nullable: true
        payment_terms:
          type: string
          nullable: true
          description: Payment terms in days. On create, defaults to the customer's customer_payment_terms when greater than zero; otherwise 30. Zero (from client or customer) is treated as unset. Due date is date + payment_terms (date_pay is not a due date).
          example: "30"
        period:
          type: string
          nullable: true
        is_period_canceled:
          type: integer
          enum: [0, 1]
          nullable: true
          description: Defaults to 0 on create when omitted or empty.
        deadline_description:
          type: string
          nullable: true
        header:
          type: string
          nullable: true
        content:
          type: string
          nullable: true
        footer:
          type: string
          nullable: true
        esr_reference_number:
          type: string
          nullable: true
        is_receipt:
          type: integer
          enum: [0, 1]
          nullable: true
          description: Defaults to 0 on create when omitted or empty.
        is_canceled:
          type: integer
          enum: [0, 1]
          nullable: true
        articles:
          type: array
          description: Required for create. On update, replaces all current document articles when provided.
          items:
            $ref: '#/components/schemas/BusinessDocumentArticleInput'

    InvoiceCreateInput:
      allOf:
        - $ref: '#/components/schemas/InvoiceInput'
        - type: object
          required:
            - customer_id
            - articles
          properties:
            articles:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/BusinessDocumentArticleInput'

    BusinessDocumentArticleInput:
      type: object
      description: Input row for offer/order/invoice article synchronization. The API sets `pos` and `pos_sort` automatically by array order. Document totals are calculated from non-optional rows only. Tax values are resolved from article master data via `article_number`; tax overrides are not accepted.
      additionalProperties: false
      example:
        article_number: "ART-1000"
        quantity: 2
        unit_price: 149.9
        reduction: 0
        is_optional: 0
      required:
        - article_number
      properties:
        article_number:
          type: string
          description: Required. The article is resolved by this number and tax values always come from the referenced article.
          example: "ART-1000"
        description:
          type: string
        quantity:
          type: number
          format: double
          description: Optional override. Default is 1 when omitted. Negative values are allowed (for example reversal or credit-note positions). Zero is invalid. For invoices, a negative document total automatically sets `is_credit` to 1.
          example: 2
        unit_price:
          type: number
          format: double
          description: Optional override. When omitted, the price from the referenced article is used. Stored position `unit_price` includes tax according to article tax settings.
          example: 149.9
        reduction:
          type: number
          format: double
          description: Optional override as percentage. Must be between 0 and 100.
          minimum: 0
          maximum: 100
          example: 0
        is_optional:
          type: integer
          description: Preferred API flag. `1` marks a row as optional, `0` marks it as non-optional. For invoices only `0` is allowed.
          enum: [0, 1]

    InvoicePaymentInput:
      type: object
      required: [invoice_id, amount]
      example:
        invoice_id: 1001
        amount: 199.5
        date: "2026-04-03"
        reference_number: "RF18539007547034"
        encashment_id: 1
        comment: "Teilzahlung"
      properties:
        invoice_id:
          type: integer
          example: 1001
        amount:
          type: number
          format: double
          example: 199.5
        date:
          type: string
          format: date
          example: "2026-04-03"
        reference_number:
          type: string
          example: "RF18539007547034"
        encashment_id:
          type: integer
          example: 1
        comment:
          type: string
        invoice_state_id:
          type: integer
          description: Optional new state for invoice after payment was inserted.

    DocumentEmailSendInput:
      type: object
      description: |
        Optional overrides for queued document email delivery.
        The API only enqueues a row in `document_email_queue` with internal `document_email_queue_status` `queued`; actual sending happens asynchronously in yourBureau.

        PDF attachments use `template_id` or `template_ids` (table `template`).
        Email text uses `subject` and `body`, defaulting from the active default `email_template` for the document type.
        Document state uses optional `state_id`, defaulting from settings `default_{offer|order|invoice}_email_state_id` after successful send.
      example:
        template_id: 12
        to: "kunde@example.org"
        cc: "team@example.org"
        bcc: "audit@example.org"
        subject: "Your invoice"
        body: "Anbei erhalten Sie Your invoice."
        state_id: 3
      properties:
        template_id:
          type: integer
          description: Optional single PDF template ID (`template` table). Mapped internally to `template_ids`. `template_ids` takes precedence when both are set.
        template_ids:
          type: string
          description: Optional comma-separated PDF template IDs (`template` table). If omitted, `template_id` or settings `default_{offer|order|invoice}_email_templates` are used at send time.
        to:
          type: string
          format: email
          description: Optional primary recipient override. Otherwise resolved from contact or customer email on the document.
        cc:
          type: string
          description: Optional comma-separated CC recipients.
        bcc:
          type: string
          description: Optional comma-separated BCC recipients.
        subject:
          type: string
          description: Optional subject override.
        body:
          type: string
          description: Optional body override. Placeholders such as `{FIRSTNAME}` are replaced during send in yourBureau.
        state_id:
          type: integer
          description: Optional new document state after successful send (`offer_state_id`, `order_state_id`, or `invoice_state_id`). If omitted, the default from settings `default_{offer|order|invoice}_email_state_id` is used at send time.

    DocumentEmailQueueResponse:
      type: object
      description: Response after queueing a document email for asynchronous delivery.
      properties:
        id:
          type: integer
          description: ID of the created `document_email_queue` row (`document_email_queue_id`).
          example: 42
        status:
          type: string
          description: Operation result (`success` when the row was queued).
          example: success
        message:
          type: string
          description: Human-readable confirmation. One of `Offer email queued successfully`, `Order email queued successfully`, or `Invoice email queued successfully`.

    OfferStateChangeInput:
      type: object
      required: [offer_id, state_id]
      example:
        offer_id: 1001
        state_id: 2
      properties:
        offer_id:
          type: integer
          example: 1001
        state_id:
          type: integer
          example: 2

    OrderStateChangeInput:
      type: object
      required: [order_id, state_id]
      example:
        order_id: 1001
        state_id: 2
      properties:
        order_id:
          type: integer
          example: 1001
        state_id:
          type: integer
          example: 2

    InvoiceStateChangeInput:
      type: object
      required: [invoice_id, state_id]
      example:
        invoice_id: 1001
        state_id: 2
      properties:
        invoice_id:
          type: integer
          example: 1001
        state_id:
          type: integer
          example: 2

    InvoiceListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Invoice'

    # -------------------------------------------------------------------------
    # Offer State
    # -------------------------------------------------------------------------
    OfferState:
      type: object
      description: Reference value for offer workflow states.
      properties:
        offer_state_id:
          type: integer
          readOnly: true
          example: 1
        offer_state:
          type: string
          example: "Open"
        special_offer_state:
          type: string
          description: Special internal state identifier
        color:
          type: string
          description: Color code for display
          example: "#ff9900"
        sort:
          type: integer
          example: 1

    OfferStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        offer_state: "Offen"
        color: "#1E90FF"
        sort: 10
      properties:
        offer_state:
          type: string
        special_offer_state:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    OfferStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OfferState'

    # -------------------------------------------------------------------------
    # Order Category
    # -------------------------------------------------------------------------
    OrderCategory:
      type: object
      description: Reference value for order categories.
      properties:
        order_category_id:
          type: integer
          readOnly: true
          example: 1
        order_category:
          type: string
          example: "Web Development"
        color:
          type: string
          description: Color code for display
          example: "#ff6600"
        sales_target:
          type: string
          example: ""
        auto_checklist_template_id:
          type: integer
          example: 0
        show_in_timetable:
          type: integer
          description: "1 = visible in timetable, 0 = hidden"
          example: 1
        timetable_new_order_autoname:
          type: string
          example: ""
        timetable_new_order_user_id_project_manager:
          type: integer
          example: 0
        accounting_account:
          type: string
          example: ""
        accounting_revenue_account:
          type: string
          example: ""
        sort:
          type: integer
          example: 1

    OrderCategoryInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        order_category: "Service"
        color: "#0099CC"
        sort: 10
      properties:
        order_category:
          type: string
        color:
          type: string
        sales_target:
          type: string
        auto_checklist_template_id:
          type: integer
        show_in_timetable:
          type: integer
        timetable_new_order_autoname:
          type: string
        timetable_new_order_user_id_project_manager:
          type: integer
        accounting_account:
          type: string
        accounting_revenue_account:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    OrderCategoryListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OrderCategory'

    # -------------------------------------------------------------------------
    # Order Scheduling Category
    # -------------------------------------------------------------------------
    OrderSchedulingCategory:
      type: object
      description: Reference value for order scheduling categories.
      properties:
        order_scheduling_category_id:
          type: integer
          readOnly: true
          example: 1
        order_scheduling_category:
          type: string
          example: "Montage"
        sort:
          type: integer
          example: 1

    OrderSchedulingCategoryInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        order_scheduling_category: "Normal"
        sort: 10
      properties:
        order_scheduling_category:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    OrderSchedulingCategoryListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OrderSchedulingCategory'

    # -------------------------------------------------------------------------
    # Order State
    # -------------------------------------------------------------------------
    OrderState:
      type: object
      description: Reference value for order workflow states.
      properties:
        order_state_id:
          type: integer
          readOnly: true
          example: 1
        order_state:
          type: string
          example: "In Progress"
        special_order_state:
          type: string
          description: Special internal state identifier
        color:
          type: string
          description: Color code for display
          example: "#0099ff"
        sort:
          type: integer
          example: 1

    OrderStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        order_state: "Geplant"
        color: "#00AA55"
        sort: 10
      properties:
        order_state:
          type: string
        special_order_state:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    OrderStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OrderState'

    # -------------------------------------------------------------------------
    # Invoice State
    # -------------------------------------------------------------------------
    InvoiceState:
      type: object
      description: Reference value for invoice workflow states.
      properties:
        invoice_state_id:
          type: integer
          readOnly: true
          example: 1
        invoice_state:
          type: string
          example: "Bezahlt"
        set_state_on_action:
          type: string
          description: Automatic state change on action
        color:
          type: string
          description: Color code for display
          example: "#00cc00"
        sort:
          type: integer
          example: 1

    InvoiceStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        invoice_state: "Offen"
        color: "#FF9900"
        sort: 10
      properties:
        invoice_state:
          type: string
        set_state_on_action:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    InvoiceStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceState'


    # -------------------------------------------------------------------------
    # Outgoing State
    # -------------------------------------------------------------------------
    OutgoingState:
      type: object
      description: Reference value for outgoing workflow states.
      properties:
        outgoing_state_id:
          type: integer
          readOnly: true
          example: 1
        outgoing_state:
          type: string
          example: "Offen"
        color:
          type: string
          example: "FF9900"
        sort:
          type: integer
          example: 1

    OutgoingStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        outgoing_state: "Bezahlt"
        color: "00cc00"
        sort: 10
      properties:
        outgoing_state:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    OutgoingStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OutgoingState'

    # -------------------------------------------------------------------------
    # Outgoing Tax
    # -------------------------------------------------------------------------
    OutgoingTax:
      type: object
      description: Reference value for outgoing tax rates.
      properties:
        outgoing_tax_id:
          type: integer
          readOnly: true
          example: 1
        outgoing_tax:
          type: string
          example: "MwSt. 8.1%"
        outgoing_tax_percentage:
          type: number
          format: double
          example: 8.1
        outgoing_tax_code:
          type: string
          example: "CH81"
        sort:
          type: integer
          example: 1
        active:
          type: integer
          enum: [0, 1]
          example: 1

    OutgoingTaxInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        outgoing_tax: "MwSt. 8.1%"
        outgoing_tax_percentage: 8.1
        outgoing_tax_code: "CH81"
        active: 1
        sort: 10
      properties:
        outgoing_tax:
          type: string
        outgoing_tax_percentage:
          type: number
          format: double
        outgoing_tax_code:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."
        active:
          type: integer
          enum: [0, 1]

    OutgoingTaxListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OutgoingTax'

    # -------------------------------------------------------------------------
    # Customer State
    # -------------------------------------------------------------------------
    CustomerState:
      type: object
      description: Reference value for customer lifecycle states.
      properties:
        customer_state_id:
          type: integer
          readOnly: true
          example: 1
        customer_state:
          type: string
          example: "Active"
        special_customer_state:
          type: string
          description: Special internal state identifier
        customer_number_prefix:
          type: string
          description: Customer number prefix
          example: "K"
        color:
          type: string
          description: Color code for display
          example: "#00ff00"
        sort:
          type: integer
          example: 1

    CustomerStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        customer_state: "Customer"
        customer_number_prefix: "K"
        color: "#009900"
        sort: 10
      properties:
        customer_state:
          type: string
        special_customer_state:
          type: string
        customer_number_prefix:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    CustomerStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/CustomerState'

    # -------------------------------------------------------------------------
    # Project State
    # -------------------------------------------------------------------------
    ProjectState:
      type: object
      description: Reference value for project lifecycle states.
      properties:
        project_state_id:
          type: integer
          readOnly: true
          example: 1
        project_state:
          type: string
          example: "Running"
        special_project_state:
          type: string
          description: Special internal state identifier
        color:
          type: string
          description: Color code for display
          example: "#3366ff"
        sort:
          type: integer
          example: 1

    ProjectStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        project_state: "Aktiv"
        color: "#3366CC"
        sort: 10
      properties:
        project_state:
          type: string
        special_project_state:
          type: string
        color:
          type: string
        sort:
          type: integer
          description: "Optional. If omitted, the API sets the next available sort value automatically."

    ProjectStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProjectState'

    # -------------------------------------------------------------------------
    # Open Task State
    # -------------------------------------------------------------------------
    OpenTaskState:
      type: object
      description: Reference value for open task states.
      properties:
        open_task_state_id:
          type: integer
          readOnly: true
          example: 1
        open_task_state:
          type: string
          example: "In Bearbeitung"
        color:
          type: string
          description: "Color code including # prefix (e.g. #FFA500)"
          example: "#FFA500"
        sort:
          type: integer
          example: 10

    OpenTaskStateInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        open_task_state: "In Bearbeitung"
        color: "#FFA500"
        sort: 10
      properties:
        open_task_state:
          type: string
          maxLength: 255
          example: "In Bearbeitung"
        color:
          type: string
          description: "Color code including # prefix (e.g. #FFA500)"
          example: "#FFA500"
        sort:
          type: integer
          example: 10

    OpenTaskStateListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OpenTaskState'

    # -------------------------------------------------------------------------
    # Open Task Category
    # -------------------------------------------------------------------------
    OpenTaskCategory:
      type: object
      description: Reference value for open task categories.
      properties:
        open_task_category_id:
          type: integer
          readOnly: true
          example: 1
        open_task_category:
          type: string
          example: "Intern"
        sort:
          type: integer
          example: 10

    OpenTaskCategoryInput:
      type: object
      description: "The `sort` field is optional and will be automatically set to the next value."
      example:
        open_task_category: "Intern"
        sort: 10
      properties:
        open_task_category:
          type: string
          maxLength: 255
          example: "Intern"
        sort:
          type: integer
          example: 10

    OpenTaskCategoryListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/OpenTaskCategory'

    # -------------------------------------------------------------------------
    # Public Holiday
    # -------------------------------------------------------------------------
    PublicHoliday:
      type: object
      description: Public holiday entry with date and optional location scoping.
      properties:
        public_holiday_id:
          type: integer
          readOnly: true
          example: 1
        public_holiday:
          type: string
          format: date
          example: "2026-12-25"
        day_period:
          type: string
          description: "Day period marker from source table (e.g. `fullday`)."
          example: "fullday"
        description:
          type: string
          example: "Weihnachtstag"
        place_id:
          type: integer
          example: 0

    PublicHolidayInput:
      type: object
      properties:
        public_holiday:
          type: string
          format: date
        day_period:
          type: string
        description:
          type: string
        place_id:
          type: integer

    PublicHolidayListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/PublicHoliday'

    # -------------------------------------------------------------------------
    # Expenses (reference)
    # -------------------------------------------------------------------------
    Expense:
      type: object
      properties:
        expenses_id:
          type: integer
          readOnly: true
          example: 12
        expense_type_id:
          type: integer
          example: 1
        expenses:
          type: string
          example: "Fahrtkosten"
        unit:
          type: string
          example: "km"
        multiplier:
          type: number
          format: double
          example: 0.70
        multiplier_external:
          type: number
          format: double
          example: 0.70
        active:
          type: integer
          example: 1

    ExpenseInput:
      type: object
      properties:
        expense_type_id:
          type: integer
          example: 1
        expenses:
          type: string
          maxLength: 255
          example: "Fahrtkosten"
        unit:
          type: string
          maxLength: 255
          example: "km"
        multiplier:
          type: number
          format: double
          example: 0.70
        multiplier_external:
          type: number
          format: double
          example: 0.70
        active:
          type: integer
          enum: [0, 1]
          example: 1

    ExpenseListResponse:
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/Meta'
        data:
          type: array
          items:
            $ref: '#/components/schemas/Expense'
