openapi: 3.0.3
info:
  title: Daxium Air API
  version: v1.3
  description: |
    ## Daxium Air REST API

    Official API documentation for **Daxium Air**, version **v1.3**.

    All calls must be encrypted using **TLS 1.2** or later, and every request
    must include the version header:

    ```
    X-Accept-Version: v1.3
    ```

    ### Authentication

    The API uses **OAuth 2.0**. Every request (except token requests) must
    include a Bearer token:

    ```
    Authorization: Bearer <access_token>
    ```

    | Token | Validity |
    |---|---|
    | `access_token` | 1 hour |
    | `refresh_token` | 42 days |

    ### Field filtering

    Any endpoint that returns objects supports limiting the returned fields:

    ```
    GET /{app_short}/any_route?fields=id,created_at
    ```

    ### Error format

    All errors return a standardised JSON body:

    ```json
    {
      "code": "DAERR-API1000",
      "message": "Invalid payload format",
      "url": "https://doc-dev.daxium-air.com/errors.en.html#DAERR-API1000",
      "api_version": "v1.3",
      "infos": [],
      "server_time": "1760616000"
    }
    ```

servers:
  - url: https://api.daxium-air.com
    description: Production

# ──────────────────────────────────── TAGS ───────────────────────────────────
tags:
  - name: auth
    x-page-title: Authorization
    x-page-description: Obtain and refresh OAuth2 tokens
    x-page-icon: lock

  - name: apps
    x-page-title: Workspace
    x-page-description: List and retrieve workspace instances (vertical métier)
    x-page-icon: layer-group

  - name: files
    x-page-title: Files
    x-page-description: Upload and download workspace-level files
    x-page-icon: file

  - name: structures
    x-page-title: Forms
    x-page-description: Retrieve form definitions and their versions
    x-page-icon: table-list

  - name: structure-relations
    x-parent: structures
    x-page-title: Form Relations
    x-page-description: Retrieve parent-child relationships between forms
    x-page-icon: sitemap

  - name: submissions
    x-page-title: Submissions
    x-page-description: Create, read, update, delete and search records
    x-page-icon: database

  - name: submission-relations
    x-parent: submissions
    x-page-title: Submission Relations
    x-page-description: Retrieve parent-child relationships between submissions
    x-page-icon: sitemap

  - name: submission-comments
    x-parent: submissions
    x-page-title: Comments
    x-page-description: Manage comments and replies on submissions
    x-page-icon: comment

  - name: customapps
    x-page-title: Custom Applications
    x-page-description: Retrieve custom mobile application configurations
    x-page-icon: mobile

  - name: lists
    x-page-title: Lists
    x-page-description: Manage hierarchical list data
    x-page-icon: list

  - name: list-access
    x-parent: lists
    x-page-title: List Access Sets
    x-page-description: Restrict list item visibility per user or group
    x-page-icon: shield

  - name: tasks
    x-page-title: Tasks
    x-page-description: Retrieve tasks linked to submissions
    x-page-icon: check-square

  - name: tasks-write
    x-parent: tasks
    x-page-title: Task Creation
    x-page-description: Create simple or recurring tasks
    x-page-icon: calendar-plus

  - name: publipostages
    x-page-title: Mail Merge
    x-page-description: Generate documents from templates and submissions
    x-page-icon: envelopes-bulk

  - name: subscriptions
    x-page-title: Subscriptions
    x-page-description: List an account's subscriptions and licenses
    x-page-icon: ticket

  - name: groups
    x-page-title: Groups
    x-page-description: Manage groups within a workspace
    x-page-icon: user-group

  - name: users
    x-page-title: Users
    x-page-description: Manage users, licenses and access
    x-page-icon: users

  - name: views
    x-page-title: Views
    x-page-description: Retrieve configured submission views
    x-page-icon: table-columns

# ──────────────────────────────────── PATHS ──────────────────────────────────
paths:

  # ── AUTHORIZATION ──────────────────────────────────────────────────────────
  /oauth/v2/token:
    post:
      summary: Get or refresh tokens
      description: |
        Use `grant_type: password` to obtain tokens for the first time, or
        `grant_type: refresh_token` to rotate them without re-entering credentials.
        Each refresh resets the access token expiry and issues a new refresh token.
      tags: [auth]
      operationId: getOrRefreshToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: "#/components/schemas/PasswordGrantRequest"
                - $ref: "#/components/schemas/RefreshTokenGrantRequest"
      responses:
        "200":
          description: Token pair issued successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TokenResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: cURL
          label: Password grant
          source: |
            curl -X POST https://api.daxium-air.com/oauth/v2/token \
              -d "client_id=YOUR_CLIENT_ID" \
              -d "client_secret=YOUR_SECRET" \
              -d "grant_type=password" \
              -d "username=user@example.com" \
              -d "password=yourpassword"
        - lang: cURL
          label: Refresh token
          source: |
            curl -X POST https://api.daxium-air.com/oauth/v2/token \
              -d "client_id=YOUR_CLIENT_ID" \
              -d "client_secret=YOUR_SECRET" \
              -d "grant_type=refresh_token" \
              -d "refresh_token=YOUR_REFRESH_TOKEN"

  # ── BUSINESS APPS ──────────────────────────────────────────────────────────
  /apps:
    get:
      summary: List all instances
      description: Returns all workspace instances accessible by the authenticated user.
      tags: [apps]
      operationId: listApps
      parameters:
        - $ref: "#/components/parameters/XAcceptVersion"
      responses:
        "200":
          description: Array of instances
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/App"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 https://api.daxium-air.com/apps

  /{app_short}:
    get:
      summary: Get a specific instance
      tags: [apps]
      operationId: getApp
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - $ref: "#/components/parameters/XAcceptVersion"
      responses:
        "200":
          description: Instance details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/App"

  # ── FILES ───────────────────────────────────────────────────────────────────
  /{app_short}/files/upload:
    post:
      summary: Upload a file
      description: |
        Upload a file at the workspace level. Once used in a submission it
        cannot be reused. Send raw binary as the body with the correct
        `Content-Type` header.
      tags: [files]
      operationId: uploadFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: File uploaded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/files/download_errors/{worker_unique_id}:
    get:
      summary: Download an error file
      description: Returns the error file generated by a background worker.
      tags: [files]
      operationId: downloadErrorFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: worker_unique_id
          in: path
          required: true
          description: Unique worker identifier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Binary file content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── FORMS ───────────────────────────────────────────────────────────────────
  /{app_short}/structures:
    get:
      summary: List all forms
      description: Returns all available forms with pagination.
      tags: [structures]
      operationId: listStructures
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: per_page
          in: query
          description: Number of forms per page (default 10)
          schema:
            type: integer
            default: 10
        - name: page
          in: query
          description: Page number (0-based)
          schema:
            type: integer
            default: 0
        - name: updated_since
          in: query
          description: Return only forms updated since this UNIX timestamp
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Paginated list of forms
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StructureListResponse"

  /{app_short}/structures/{id}:
    get:
      summary: Get a form
      description: Returns the form at the specified version, or the latest version if omitted.
      tags: [structures]
      operationId: getStructure
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Form identifier
          schema:
            type: integer
        - name: version
          in: query
          description: Form version number
          schema:
            type: integer
      responses:
        "200":
          description: Form object
          content:
            application/json:
              schema:
                type: object
                properties:
                  structure:
                    $ref: "#/components/schemas/Structure"
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/structures/{id}/file/{file_id}:
    get:
      summary: Get a file attached to a form
      description: Returns the file attached to a form (currently Logo field images only).
      tags: [structures]
      operationId: getStructureFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: integer
        - name: file_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Image file
          content:
            image/jpeg:
              schema:
                type: string
                format: binary

  # ── FORM RELATIONS ──────────────────────────────────────────────────────────
  /{app_short}/structure_relations/{structure_id}/parents:
    get:
      summary: Get parent forms of a form
      tags: [structure-relations]
      operationId: getStructureParents
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: path
          required: true
          schema:
            type: string
        - name: per_page
          in: query
          schema:
            type: integer
        - name: page
          in: query
          schema:
            type: integer
        - name: updated_since
          in: query
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Parent form IDs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StructureRelationPagedResponse"

  # ── SUBMISSION RELATIONS ────────────────────────────────────────────────────
  /{app_short}/submission_relations/{submission_uuid}/parents:
    get:
      summary: Get parent submissions of a submission
      tags: [submission-relations]
      operationId: getSubmissionParents
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submission_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: updated_since
          in: query
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Parent submission IDs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionRelationPagedResponse"

  # ── SUBMISSIONS ─────────────────────────────────────────────────────────────
  /{app_short}/submissions:
    get:
      summary: List submissions
      description: |
        Returns submissions for a form with pagination.
        Set `reference=true` to return reference data across all forms
        (omit `structure_id` in that case).
      tags: [submissions]
      operationId: listSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          description: Form identifier (required unless `reference=true`)
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
        - name: page
          in: query
          schema:
            type: integer
        - name: updated_since
          in: query
          schema:
            type: integer
            format: int64
        - name: items_expanded
          in: query
          description: Return list labels instead of IDs only
          schema:
            type: boolean
        - name: reference
          in: query
          description: "Set to `true` to return all reference data across all forms (shared reference lists used to prefill fields). Omit `structure_id` in that case."
          schema:
            type: boolean
      responses:
        "200":
          description: Paginated submissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionListResponse"
      x-codeSamples:
        - lang: cURL
          label: By form
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/submissions?structure_id=1"

    post:
      summary: Create a submission
      description: |
        Creates a new submission. For `image`, `signature`, or `file` fields,
        upload the file first and use the returned UUID here.
      tags: [submissions]
      operationId: createSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: partial
          in: query
          description: If `true`, ignores required/editable field permissions
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Submission created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl -X POST \
                 -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 -H "Content-Type: application/json" \
                 -d '{"structure_id":1,"structure_version":3,"items":{"string4":"Hello"}}' \
                 https://api.daxium-air.com/daxium-test/submissions

  /{app_short}/submissions/byids:
    post:
      summary: Get submissions by IDs (up to 500)
      description: Returns submissions for up to 500 UUIDs. Each ID can optionally carry an `updated_since` filter.
      tags: [submissions]
      operationId: getSubmissionsByIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: items_expanded
          in: query
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [submission_ids]
              properties:
                submission_ids:
                  type: array
                  maxItems: 500
                  items:
                    type: object
                    required: [id]
                    properties:
                      id:
                        type: string
                        format: uuid
                      updated_since:
                        type: integer
                        format: int64
      responses:
        "200":
          description: Matching submissions
          content:
            application/json:
              schema:
                type: object
                properties:
                  submissions:
                    type: array
                    items:
                      $ref: "#/components/schemas/Submission"
        "400":
          description: Too many IDs (max 500)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/search:
    post:
      summary: Search submissions
      description: |
        Returns submissions matching field-level criteria. Each field can appear
        only once. Operator case is significant.

        | Operator | Compatible field types |
        |---|---|
        | `equal` / `notequal` | text, number, boolean, date, email, phone |
        | `isNull` / `isNotNull` | all |
        | `less` / `greater` / `lesseq` / `greatereq` | number |
        | `contains` / `notContains` / `startWith` / `endWith` | text, email, phone |
        | `in` | list, user, text |
        | `range` | number, date |
      tags: [submissions]
      operationId: searchSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          required: true
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
        - name: page
          in: query
          schema:
            type: integer
        - name: last_updated_user_id
          in: query
          description: "Filters submissions whose last update was performed by the user with this ID. Optional criterion passed directly in the URL."
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchCriteria"
      responses:
        "200":
          description: Matching submissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionListResponse"

  /{app_short}/submissions/multiple:
    post:
      summary: Create multiple submissions (async)
      description: Creates up to 100 submissions asynchronously. Poll `callbackUrl` for status.
      tags: [submissions]
      operationId: createMultipleSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Async job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Empty payload or more than 100 submissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    put:
      summary: Update multiple submissions (async)
      description: Updates up to 100 submissions asynchronously. Poll `callbackUrl` for status.
      tags: [submissions]
      operationId: updateMultipleSubmissions
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Async job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Empty payload or more than 100 submissions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/multiple/callback/{callback_id}:
    get:
      summary: Poll async submission job status
      tags: [submissions]
      operationId: getMultipleSubmissionsCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job status and per-submission results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionCallbackResponse"

  /{app_short}/submissions/upload:
    post:
      summary: Upload a file for a submission
      description: Uploads a file to attach to a submission field (image, signature, file type).
      tags: [submissions]
      operationId: uploadSubmissionFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: File uploaded — use the returned `uuid` in the submission field
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/submissions/upload/{submissionId}/{fileId}:
    post:
      summary: Upload a file with explicit submission and file IDs
      description: Alternative upload that pre-assigns both the submission UUID and file UUID.
      tags: [submissions]
      operationId: uploadSubmissionFileWithIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submissionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: fileId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: File uploaded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUploadResponse"

  /{app_short}/submissions/comments:
    post:
      summary: Get comments for multiple submissions
      description: |
        Returns comments for up to **1000** submission IDs in one request.
      tags: [submission-comments]
      operationId: getCommentsBySubmissionIds
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [submission_ids]
              properties:
                submission_ids:
                  type: array
                  maxItems: 1000
                  items:
                    type: string
                    format: uuid
      responses:
        "200":
          description: Comments grouped by submission UUID
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    $ref: "#/components/schemas/Comment"
        "400":
          description: Too many submission IDs — max 1000 (DAERR-COM1010)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}:
    get:
      summary: Get a submission
      tags: [submissions]
      operationId: getSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Submission object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    put:
      summary: Update a submission
      description: |
        Replaces submission data. For workflow-enabled forms, include `trigger_id`
        to advance the state. Submissions in an end-state cannot be modified.
      tags: [submissions]
      operationId: updateSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: partial
          in: query
          description: If `true`, ignores required/editable field permissions
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Updated submission
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    patch:
      summary: Partially update a submission
      description: Updates only the `items` supplied; all other fields remain unchanged.
      tags: [submissions]
      operationId: patchSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmissionInput"
      responses:
        "200":
          description: Full updated submission returned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"

    delete:
      summary: Delete a submission
      tags: [submissions]
      operationId: deleteSubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Submission deleted

  /{app_short}/submissions/{id}/file/{file_id}:
    get:
      summary: Get a file attached to a submission
      tags: [submissions]
      operationId: getSubmissionFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: file_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Binary file content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  /{app_short}/submissions/{id}/comments/count:
    get:
      summary: Count comments on a submission
      description: Returns the total number of comments linked to a submission.
      tags: [submission-comments]
      operationId: countSubmissionComments
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          description: Submission identifier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Total number of comments
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                    description: Total number of comments linked to the submission

  /{app_short}/submissions/{id}/comments:
    get:
      summary: List comments on a submission
      tags: [submission-comments]
      operationId: listSubmissionComments
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: List of comments (including replies)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Comment"
        "403":
          description: Access to submission forbidden (DAERR-COM1000)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Submission not found (DAERR-COM1006)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    post:
      summary: Create a comment on a submission
      description: |
        Adds a new comment. Plain text only — no HTML or script content.
        Maximum **1000 characters**. Supports an optional client-supplied UUID.
      tags: [submission-comments]
      operationId: createSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Comment created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "400":
          description: |
            Validation error — missing message (DAERR-COM1002), too long
            (DAERR-COM1003), invalid date (DAERR-COM1004), invalid characters
            (DAERR-COM1005), invalid UUID (DAERR-COM1009), or comment limit
            reached (DAERR-COM1012)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Access forbidden (DAERR-COM1000)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}/comments/{comment_id}:
    put:
      summary: Update a comment
      description: Updates the text of a comment. Users can only update their own comments (DAERR-COM1008).
      tags: [submission-comments]
      operationId: updateSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Comment updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "403":
          description: Not allowed to update this comment (DAERR-COM1008)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Comment not found (DAERR-COM1001)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    delete:
      summary: Delete a comment
      description: Deletes a comment. Users can only delete their own comments (DAERR-COM1007).
      tags: [submission-comments]
      operationId: deleteSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Comment deleted
        "403":
          description: Not allowed to delete this comment (DAERR-COM1007)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Comment not found (DAERR-COM1001)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/submissions/{id}/comments/{comment_id}/replies:
    post:
      summary: Reply to a comment
      description: |
        Adds a reply to a first-level comment (max thread depth is 2).
        Replying to a reply is not allowed (DAERR-COM1011).
      tags: [submission-comments]
      operationId: replyToSubmissionComment
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: comment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "200":
          description: Reply created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Comment"
        "403":
          description: Cannot reply to a second-level comment (DAERR-COM1011)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── CUSTOM APPLICATIONS ─────────────────────────────────────────────────────
  /{app_short}/customapps:
    get:
      summary: List custom apps
      description: |
        Returns all custom applications in short format. If none are configured,
        returns the default application in full format.
      tags: [customapps]
      operationId: listCustomApps
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: List of custom apps
          content:
            application/json:
              schema:
                type: object
                properties:
                  apps:
                    type: array
                    items:
                      $ref: "#/components/schemas/CustomAppSummary"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/customapps/{app_id}:
    get:
      summary: Get a custom app
      tags: [customapps]
      operationId: getCustomApp
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: app_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Custom app details
          content:
            application/json:
              schema:
                type: object
                properties:
                  app:
                    $ref: "#/components/schemas/CustomApp"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64
                  required:
                    type: object
                    properties:
                      files_uuid:
                        type: array
                        items:
                          type: string
                          format: uuid

  /{app_short}/customapps/{app_id}/file/{file_uuid}:
    get:
      summary: Get a file from a custom app
      tags: [customapps]
      operationId: getCustomAppFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: app_id
          in: path
          required: true
          schema:
            type: integer
        - name: file_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Binary file content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── LISTS ───────────────────────────────────────────────────────────────────
  /{app_short}/lists:
    get:
      summary: List all lists (root IDs only)
      description: Returns all root list identifiers in the workspace without their child items.
      tags: [lists]
      operationId: listLists
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: per_page
          in: query
          schema:
            type: integer
            default: 10
            maximum: 1000
        - name: page
          in: query
          schema:
            type: integer
            default: 0
        - name: updated_since
          in: query
          description: Return only lists updated after this date (including deleted ones)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Paginated root list entries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListPagedResponse"

    post:
      summary: Create a list
      description: Creates a root node with optional hierarchical children via `items`.
      tags: [lists]
      operationId: createList
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListCreateRequest"
      responses:
        "201":
          description: Root list node created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItem"

  /{app_short}/lists/import:
    post:
      summary: Import a list from CSV (async)
      description: |
        Imports or diffs list data from a previously uploaded CSV file.
        Poll the returned `callbackUrl` for status.
      tags: [lists]
      operationId: importList
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [list_id, file_id]
              properties:
                list_id:
                  type: integer
                file_id:
                  type: string
                  format: uuid
                separator:
                  type: string
                  default: ";"
                mode:
                  type: string
                  enum: [APPEND, DIFF]
                encoding:
                  type: string
                  enum: [UTF-8, WINDOWS-1252, WINDOWS-1256]
                  default: UTF-8
      responses:
        "200":
          description: Async job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Validation error (missing file_id, bad encoding, file not found, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/lists/updated:
    post:
      summary: Get modified list and access-set IDs
      description: Returns IDs of lists and access sets that have changed and need syncing.
      tags: [lists]
      operationId: getUpdatedLists
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                list_states:
                  type: array
                  items:
                    type: object
                    properties:
                      list_id:
                        type: integer
                      list_updated_since:
                        type: integer
                        format: int64
                      access_set_updated_since:
                        type: integer
                        format: int64
      responses:
        "200":
          description: IDs of lists and access sets requiring an update
          content:
            application/json:
              schema:
                type: object
                properties:
                  lists_to_update:
                    type: array
                    items:
                      type: integer
                  lists_with_access_set:
                    type: array
                    items:
                      type: integer
                  access_set_to_update:
                    type: array
                    items:
                      type: integer

  /{app_short}/lists/callback/{callback_id}:
    get:
      summary: Poll async list job status
      tags: [lists]
      operationId: getListCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListCallbackResponse"

  /{app_short}/lists/{list_id}:
    get:
      summary: Get a list with all its items
      description: Returns the list and all child items with pagination.
      tags: [lists]
      operationId: getList
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
            maximum: 1000
        - name: page
          in: query
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Return only items updated after this date (including deleted ones)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Paginated list items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

    delete:
      summary: Delete a complete list
      description: Permanently removes the root node and all children. Target must be a root node.
      tags: [lists]
      operationId: deleteList
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: List deleted

  /{app_short}/lists/{root_id}:
    patch:
      summary: Edit list item names
      description: |
        Updates the `name` of one or more items identified by their `id`.
        Send an array of `{id, name}` objects.
      tags: [lists]
      operationId: editListItems
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Root node identifier
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                required: [id, name]
                properties:
                  id:
                    type: integer
                  name:
                    type: string
      responses:
        "200":
          description: Updated items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

  /{app_short}/lists/{list_id}/selectables:
    get:
      summary: Get selectable item IDs for a list
      description: Returns all item IDs the authenticated user can select, respecting access sets.
      tags: [lists]
      operationId: getListSelectables
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Selectable item IDs
          content:
            application/json:
              schema:
                type: object
                properties:
                  selectable_ids:
                    type: array
                    items:
                      type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/lists/{root_id}/search:
    post:
      summary: Search list items by external_id
      tags: [lists]
      operationId: searchListItems
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [external_id]
              properties:
                external_id:
                  type: string
      responses:
        "200":
          description: Matching list items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ListItem"

  /{app_short}/lists/{root_id}/child/{parent_id}:
    get:
      summary: Get direct children of a node
      tags: [lists]
      operationId: getListChildren
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: parent_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Direct child items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListItemPagedResponse"

  /{app_short}/lists/{root_id}/{list_id}:
    put:
      summary: Update a list item
      tags: [lists]
      operationId: updateListItem
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListItemUpdateRequest"
      responses:
        "200":
          description: Item updated
          content:
            application/json:
              schema:
                type: array
                items: {}

    patch:
      summary: Bulk add children to a node
      description: Appends multiple children to a parent node in one request.
      tags: [lists]
      operationId: addListChildren
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          description: Parent node identifier
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/ListItemUpdateRequest"
      responses:
        "200":
          description: Children added
          content:
            application/json:
              schema:
                type: array
                items: {}

    delete:
      summary: Delete a list item
      description: Removes the item and all its children recursively.
      tags: [lists]
      operationId: deleteListItem
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Item deleted

  /{app_short}/lists/{root_id}/{list_id}/image:
    get:
      summary: Get the image for a list item
      description: Returns the image — only when `has_image` is `true`.
      tags: [lists]
      operationId: getListItemImage
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Image content
          content:
            image/jpeg:
              schema:
                type: string
                format: binary

  /{app_short}/lists/{root_id}/{node_to_move_id}/{direction}/{node_position_id}:
    put:
      summary: Reposition a list node
      description: |
        Moves a node relative to a reference node.

        | `direction` | Effect |
        |---|---|
        | `child` | Becomes the first child of the reference node |
        | `before` | Placed just before the reference node (same parent) |
        | `after` | Placed just after the reference node (same parent) |
      tags: [lists]
      operationId: moveListNode
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          schema:
            type: integer
        - name: node_to_move_id
          in: path
          required: true
          schema:
            type: integer
        - name: direction
          in: path
          required: true
          schema:
            type: string
            enum: [child, before, after]
        - name: node_position_id
          in: path
          required: true
          description: Reference node for placement
          schema:
            type: integer
      responses:
        "200":
          description: Node repositioned

  # ── LIST ACCESS SETS ─────────────────────────────────────────────────────────
  /{app_short}/list/accessset:
    get:
      summary: List all access sets
      tags: [list-access]
      operationId: listAccessSets
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: All access sets
          content:
            application/json:
              schema:
                type: object
                properties:
                  listaccesssets:
                    type: array
                    items:
                      $ref: "#/components/schemas/ListAccessSetSummary"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/list/{root_id}/accessset:
    post:
      summary: Create an access set
      tags: [list-access]
      operationId: createAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: root_id
          in: path
          required: true
          description: Root list node identifier
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                groups:
                  type: array
                  items:
                    type: integer
                users:
                  type: array
                  items:
                    type: integer
                lists:
                  type: array
                  items:
                    type: integer
      responses:
        "200":
          description: Access set created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid

  /{app_short}/list/{list_id}/accessset/user/{user_id}:
    get:
      summary: Get access sets for a list and user
      tags: [list-access]
      operationId: getAccessSetsForUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: user_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Access set UUIDs for this user and list
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
                  format: uuid

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}:
    get:
      summary: Get access set details
      tags: [list-access]
      operationId: getAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Access set full details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAccessSetDetail"

    delete:
      summary: Delete an access set
      tags: [list-access]
      operationId: deleteAccessSet
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Access set deleted

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/generate:
    post:
      summary: Generate access set dependencies
      description: |
        Immediately recalculates internal access set dependencies.
        Runs automatically every 5 minutes; call manually after batch changes.
      tags: [list-access]
      operationId: generateAccessSetDependencies
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Dependencies regenerated

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/{entity}:
    get:
      summary: Get access set entity list (users / groups / lists)
      description: Use `users`, `groups`, or `lists` as the `entity` path value.
      tags: [list-access]
      operationId: getAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [users, groups, lists]
      responses:
        "200":
          description: Array of the requested entity type
          content:
            application/json:
              schema:
                type: array
                items: {}

  /{app_short}/list/{list_id}/accessset/{list_access_set_id}/{entity}/{entity_id}:
    put:
      summary: Add an entity to an access set
      description: Adds a user, group, or list item to the access set.
      tags: [list-access]
      operationId: addAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [users, groups, lists]
        - name: entity_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Entity added

    delete:
      summary: Remove an entity from an access set
      description: Removes a user, group, or list item from the access set.
      tags: [list-access]
      operationId: deleteAccessSetEntity
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: list_id
          in: path
          required: true
          schema:
            type: integer
        - name: list_access_set_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [users, groups, lists]
        - name: entity_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Entity removed

  # ── TASKS ───────────────────────────────────────────────────────────────────
  /{app_short}/tasks:
    get:
      summary: List tasks
      description: |
        Returns tasks filtered by user, form, and/or date range.
        All date parameters are UNIX timestamps.
        Use `me` (no value required) to return tasks for the authenticated user.
      tags: [tasks]
      operationId: listTasks
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure_id
          in: query
          description: Filter by form identifier
          schema:
            type: string
        - name: greater_start_date
          in: query
          description: Start date ≥ this timestamp
          schema:
            type: integer
            format: int64
        - name: lower_start_date
          in: query
          description: Start date ≤ this timestamp
          schema:
            type: integer
            format: int64
        - name: greater_due_date
          in: query
          description: Due date ≥ this timestamp
          schema:
            type: integer
            format: int64
        - name: lower_due_date
          in: query
          description: Due date ≤ this timestamp
          schema:
            type: integer
            format: int64
        - name: user_id
          in: query
          description: Filter by user ID
          schema:
            type: integer
        - name: me
          in: query
          description: If present, return only the authenticated user's tasks
          schema:
            type: boolean
        - name: page
          in: query
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Paginated list of tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskListResponse"
      x-codeSamples:
        - lang: cURL
          label: My tasks
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/tasks?me=true"
        - lang: cURL
          label: By date range
          source: |
            curl -H "Authorization: Bearer $TOKEN" \
                 -H "X-Accept-Version: v1.3" \
                 "https://api.daxium-air.com/daxium-test/tasks?greater_start_date=1700000000&lower_due_date=1702000000"

    post:
      summary: Create a task (simple or recurring)
      description: |
        Creates a task linked to a submission model (`submission_model_id`) or to a list of
        submissions (`submissions`). For a **recurring** task, add the `recurrency` object;
        in that case `due_date` is not required.
      tags: [tasks-write]
      operationId: createTask
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskInput"
      responses:
        "200":
          description: Task created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"

  # ── MAIL MERGE ───────────────────────────────────────────────────────────────
  /{app_short}/report_templates:
    get:
      summary: List mail-merge templates
      description: |
        Returns the mail-merge templates available for a form (`structure`) or a
        submission (`submission`). Provide one **or** the other parameter.
      tags: [publipostages]
      operationId: listReportTemplates
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: structure
          in: query
          description: Form identifier whose templates to retrieve
          schema:
            type: integer
        - name: submission
          in: query
          description: Submission identifier (UUID) whose templates to retrieve
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Available mail-merge templates
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportTemplateListResponse"

  /{app_short}/reports:
    get:
      summary: List mail merges for a submission
      description: Returns all mail merges generated for a given submission.
      tags: [publipostages]
      operationId: listReportsBySubmission
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: submission
          in: query
          required: true
          description: Submission identifier (UUID)
          schema:
            type: string
            format: uuid
        - name: per_page
          in: query
          description: Number of mail merges per page (default 10, max 1000)
          schema:
            type: integer
        - name: page
          in: query
          description: Number of the requested page
          schema:
            type: integer
        - name: updated_since
          in: query
          description: Filter mail merges updated after this date (timestamp)
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: Paginated list of mail merges
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportListResponse"

    post:
      summary: Start a mail merge
      description: |
        Starts mail-merge generation for a submission from a template.
        The `api_callback` URL is called (PUT method) once generation completes;
        `{publipostage_id}` is replaced by the identifier of the created mail merge.
      tags: [publipostages]
      operationId: startReport
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReportInput"
      responses:
        "200":
          description: Mail merge created
          content:
            application/json:
              schema:
                type: object
                properties:
                  report_id:
                    type: integer
                    description: Identifier of the created mail merge

  /{app_short}/reports/{report_id}:
    get:
      summary: Get mail merge information
      description: Returns the status and generated files of a mail merge by its identifier.
      tags: [publipostages]
      operationId: getReport
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: report_id
          in: path
          required: true
          description: Mail merge identifier
          schema:
            type: integer
      responses:
        "200":
          description: Mail merge information
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Report"

  /{app_short}/reports/{report_id}/results/{file_uuid}:
    get:
      summary: Download a generated mail-merge file
      description: Returns the binary file generated by a mail merge from its UUID.
      tags: [publipostages]
      operationId: getReportResultFile
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: report_id
          in: path
          required: true
          description: Mail merge identifier
          schema:
            type: integer
        - name: file_uuid
          in: path
          required: true
          description: UUID of the generated file
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Binary content of the generated file
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  # ── SUBSCRIPTIONS ────────────────────────────────────────────────────────────
  /subscriptions:
    get:
      summary: List an account's subscriptions
      description: |
        Returns all subscriptions linked to the account. To log in, a user must hold a web
        and/or mobile license attached to a subscription. This endpoint is not prefixed by `{app_short}`.
      tags: [subscriptions]
      operationId: listSubscriptions
      responses:
        "200":
          description: List of available subscriptions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubscriptionListResponse"

  # ── GROUPS ───────────────────────────────────────────────────────────────────
  /{app_short}/groups:
    get:
      summary: List groups of a workspace
      description: Returns all groups of the workspace. Requires administrator rights.
      tags: [groups]
      operationId: listGroups
      parameters:
        - $ref: "#/components/parameters/AppShort"
      responses:
        "200":
          description: List of groups
          content:
            application/json:
              schema:
                type: object
                properties:
                  groups:
                    type: array
                    items:
                      $ref: "#/components/schemas/Group"
                  total_count:
                    type: integer
                  total_pages:
                    type: integer
                  server_time:
                    type: integer
                    format: int64

  /{app_short}/group:
    post:
      summary: Create a group
      tags: [groups]
      operationId: createGroup
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupInput"
      responses:
        "200":
          description: Group created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"

  /{app_short}/group/{group_id}:
    put:
      summary: Update a group
      tags: [groups]
      operationId: updateGroup
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: group_id
          in: path
          required: true
          description: Group identifier
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupInput"
      responses:
        "200":
          description: Group updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"

  # ── USERS ────────────────────────────────────────────────────────────────────
  /users:
    get:
      summary: List all users
      description: Returns all users of the account. This endpoint is not prefixed by `{app_short}`.
      tags: [users]
      operationId: listAllUsers
      responses:
        "200":
          description: List of users
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserListResponse"

  /users/me:
    get:
      summary: Get the current user
      description: Returns the authenticated user and the associated company account.
      tags: [users]
      operationId: getCurrentUser
      responses:
        "200":
          description: Current user and company
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUserResponse"

  /users/pushkey:
    post:
      summary: Send a push key
      description: Registers a push notification key for one of the user's devices.
      tags: [users]
      operationId: sendPushKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushKeyInput"
      responses:
        "200":
          description: Push key registered
          content:
            application/json:
              schema:
                type: object

  /{app_short}/users:
    get:
      summary: List workspace users (or look up by email)
      description: |
        Returns the workspace users. If the `username` parameter is provided,
        returns the single user matching that email.
      tags: [users]
      operationId: listAppUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: username
          in: query
          description: Email address to retrieve a specific user
          schema:
            type: string
            format: email
      responses:
        "200":
          description: List of users, or a single user when `username` is provided
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/UserListResponse"
                  - $ref: "#/components/schemas/User"

    post:
      summary: Create a user
      description: |
        Creates a user. Provide `plainPassword` for Daxium authentication, or `ssoProvider`
        for authentication via the company's SSO provider.
      tags: [users]
      operationId: createUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: User created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          description: Invalid data (password, ssoProvider, groups or subscription invalid)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: The email (username) already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/users/multiple:
    post:
      summary: Create multiple users (async)
      description: Creates up to 100 users asynchronously. Poll the returned `callbackUrl`.
      tags: [users]
      operationId: createMultipleUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Async job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Empty payload or more than 100 users
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

    put:
      summary: Update multiple users (async)
      description: Updates up to 100 users asynchronously. Poll the returned `callbackUrl`.
      tags: [users]
      operationId: updateMultipleUsers
      parameters:
        - $ref: "#/components/parameters/AppShort"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              maxItems: 100
              items:
                $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: Async job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallbackUrlResponse"
        "400":
          description: Empty payload or more than 100 users
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /{app_short}/users/multiple/callback/{callback_id}:
    get:
      summary: Poll async user job status
      tags: [users]
      operationId: getMultipleUsersCallback
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: callback_id
          in: path
          required: true
          description: Callback identifier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Status and per-user results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsersCallbackResponse"

  /{app_short}/users/{user_id}:
    get:
      summary: Get a user by identifier
      tags: [users]
      operationId: getUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: user_id
          in: path
          required: true
          description: User identifier
          schema:
            type: integer
      responses:
        "200":
          description: User object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

    patch:
      summary: Update a user
      tags: [users]
      operationId: updateUser
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: user_id
          in: path
          required: true
          description: User identifier
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserInput"
      responses:
        "200":
          description: User updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  # ── VIEWS ────────────────────────────────────────────────────────────────────
  /{app_short}/submissions_views/{view_id}:
    get:
      summary: Get a view
      description: |
        Returns a configured submission view: display mode, ordered fields, and
        filtering rules (on items and on metadata).
      tags: [views]
      operationId: getView
      parameters:
        - $ref: "#/components/parameters/AppShort"
        - name: view_id
          in: path
          required: true
          description: View identifier (UUID)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: View object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/View"

# ────────────────────────────────── COMPONENTS ───────────────────────────────
components:

  parameters:
    AppShort:
      name: app_short
      in: path
      required: true
      description: Short name of the workspace instance (e.g. `daxium-test`)
      schema:
        type: string
    XAcceptVersion:
      name: X-Accept-Version
      in: header
      required: true
      description: API version to target
      schema:
        type: string
        default: v1.3
        enum: [v1.3]

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

  responses:
    Unauthorized:
      description: Invalid or expired token
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:

    # ── Error ─────────────────────────────────────────────────────────────────
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          example: DAERR-API1000
          description: Unique error code prefixed with `DAERR-`
        message:
          type: string
        url:
          type: string
          format: uri
          description: Link to the detailed error documentation
        api_version:
          type: string
          example: v1.3
        infos:
          type: array
          items: {}
          description: Additional context (may be empty)
        server_time:
          type: string
          description: Unix timestamp as string

    # ── Auth ──────────────────────────────────────────────────────────────────
    PasswordGrantRequest:
      type: object
      required: [client_id, client_secret, grant_type, username, password]
      properties:
        client_id:
          type: string
        client_secret:
          type: string
        grant_type:
          type: string
          enum: [password]
        username:
          type: string
          format: email
        password:
          type: string
          format: password

    RefreshTokenGrantRequest:
      type: object
      required: [client_id, client_secret, grant_type, refresh_token]
      properties:
        client_id:
          type: string
        client_secret:
          type: string
        grant_type:
          type: string
          enum: [refresh_token]
        refresh_token:
          type: string

    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
        refresh_token:
          type: string
        expires_in:
          type: integer
          description: Validity in seconds (3600)
        token_type:
          type: string
          example: bearer
        scope:
          type: string

    # ── Apps ──────────────────────────────────────────────────────────────────
    App:
      type: object
      properties:
        name:
          type: string
        short:
          type: string
          description: Use this as the `{app_short}` prefix in subsequent API calls
        description:
          type: string
        settings:
          type: object
          additionalProperties: true
        android_map:
          type: boolean
        is_model:
          type: boolean

    # ── Files ─────────────────────────────────────────────────────────────────
    FileUploadResponse:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Reference this UUID in submission fields of type image/signature/file
        mime-type:
          type: string
        size:
          type: integer
          description: File size in bytes

    # ── Structures ────────────────────────────────────────────────────────────
    Structure:
      type: object
      properties:
        id:
          type: integer
        version:
          type: integer
        name:
          type: string
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64
        settings:
          type: object
          additionalProperties: true
        conditions:
          type: array
          items:
            $ref: "#/components/schemas/Condition"
        fields:
          type: array
          items:
            $ref: "#/components/schemas/Field"
        layout:
          type: object
          properties:
            pages:
              type: array
              items: {}
            fields:
              type: array
              items: {}
        workflows:
          type: array
          items:
            $ref: "#/components/schemas/Workflow"
        triggers:
          type: array
          items:
            $ref: "#/components/schemas/Trigger"

    StructureListResponse:
      type: object
      properties:
        structures:
          type: array
          items:
            $ref: "#/components/schemas/Structure"
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64
        workflows:
          type: array
          items:
            $ref: "#/components/schemas/Workflow"
        required:
          type: object
          properties:
            linked_list_ids:
              type: array
              items:
                type: integer
            structure_ids:
              type: array
              items:
                type: integer

    Field:
      type: object
      properties:
        name:
          type: string
          description: System name — used as key in submission `items`
        label:
          type: string
        position:
          type: integer
        type:
          type: string
          description: >
            text, number, boolean, date, duration, phone, email,
            location, image, signature, file, list, relation, user, label…
        enable:
          type: boolean
        required:
          type: boolean
        prefill:
          type: boolean
        readonly:
          type: boolean
        searchable:
          type: boolean

    Condition:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        name:
          type: string
        cdt:
          type: string
          description: JavaScript condition expression
        fields:
          type: array
          items:
            type: string
        json_format:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              value:
                type: string
              operator:
                type: string

    Workflow:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64
        start_state:
          type: string
          format: uuid
        states:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowState"
        transitions:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowTransition"

    WorkflowState:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        color:
          type: string
        end:
          type: boolean
          description: Submissions in end states cannot be modified

    WorkflowTransition:
      type: object
      properties:
        id:
          type: string
          format: uuid
        from:
          type: string
          format: uuid
        to:
          type: string
          format: uuid
        label:
          type: string

    Trigger:
      type: object
      description: Customises how a workflow transition appears as a save button
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
        transition_id:
          type: string
          format: uuid
        color:
          type: string
        label:
          type: string

    # ── Relations ─────────────────────────────────────────────────────────────
    StructureRelationPagedResponse:
      type: object
      properties:
        structure_relations:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    SubmissionRelationPagedResponse:
      type: object
      properties:
        submission_relations:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    # ── Submissions ───────────────────────────────────────────────────────────
    Submission:
      type: object
      properties:
        id:
          type: string
          format: uuid
        structure_id:
          type: integer
        structure_version:
          type: integer
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64
        data_created_at:
          type: integer
          format: int64
        data_updated_at:
          type: integer
          format: int64
        user_id:
          type: integer
        channels:
          type: array
          items:
            type: string
        items:
          type: object
          additionalProperties: true
          description: |
            Key/value map keyed by system field name.

            **Simple:** text/email → string | number → float | boolean → bool |
            date → Unix timestamp | duration → ms | phone → RFC 3966

            **location:** `{lat, lng, address}`

            **image/signature/file:** `[{id, name, mimeType, extension, size, comment}]`

            **list:** `[id, …]`

            **relation:** `{submissions: [{id, count?}, …]}`

            **user:** `[{id}, …]`
        submission_number:
          type: integer
        number_in_structure:
          type: integer
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        last_updated_user_id:
          type: integer
        current_state:
          type: string
          format: uuid
          nullable: true
          description: Current workflow state UUID
        assigned_user:
          $ref: "#/components/schemas/UserRef"
        subscribers:
          type: array
          items: {}
        settings:
          type: array
          items: {}

    SubmissionInput:
      type: object
      required: [structure_id, structure_version, items]
      properties:
        id:
          type: string
          format: uuid
          description: Auto-generated if omitted
        structure_id:
          type: integer
        structure_version:
          type: integer
        created_at:
          type: integer
          format: int64
          description: Defaults to now
        updated_at:
          type: integer
          format: int64
          description: Defaults to now
        trigger_id:
          type: string
          format: uuid
          description: Workflow transition to apply (PUT/PATCH only)
        substitute_user_id:
          type: integer
          description: Sets a different user as creator/owner; authenticated user is always traced in history
        items:
          type: object
          additionalProperties: true

    SubmissionListResponse:
      type: object
      properties:
        submissions:
          type: array
          items:
            $ref: "#/components/schemas/Submission"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    SearchCriteria:
      type: object
      additionalProperties:
        type: object
        required: [operator]
        properties:
          value:
            description: Search value (omit for isNull / isNotNull)
          operator:
            type: string
            enum:
              - equal
              - notequal
              - isNull
              - isNotNull
              - less
              - greater
              - lesseq
              - greatereq
              - contains
              - notContains
              - startWith
              - endWith
              - in
              - range
      description: Object where each key is a valid system field name

    CallbackUrlResponse:
      type: object
      properties:
        callbackUrl:
          type: string
          description: URL to poll for async job status

    SubmissionCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
                - In timeout
            nbRecordsTodo:
              type: integer
            nbRecordsDone:
              type: integer
            nbSuccess:
              type: integer
            nbErrors:
              type: integer
            success:
              type: array
              items:
                type: string
                format: uuid
            errors:
              type: array
              items:
                type: object
                additionalProperties:
                  type: array
                  items:
                    type: string

    UserRef:
      type: object
      properties:
        id:
          type: integer
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        groups:
          type: array
          items: {}
        system_groups:
          type: array
          items: {}

    # ── Comments ──────────────────────────────────────────────────────────────
    Comment:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Comment identifier (UUID v4)
        message:
          type: string
          maxLength: 1000
          description: Plain text only — no HTML or script tags
        client_created_at:
          type: integer
          format: int64
          description: Client-side creation timestamp (Unix timestamp)
        client_updated_at:
          type: integer
          format: int64
          description: Client-side update timestamp (Unix timestamp)
        user_id:
          type: integer
        submission_id:
          type: string
          format: uuid
        replies:
          type: array
          description: Second-level replies (thread depth is limited to 2)
          items:
            $ref: "#/components/schemas/Comment"

    CommentInput:
      type: object
      required: [message]
      properties:
        id:
          type: string
          format: uuid
          description: Optional client-supplied UUID v4
        message:
          type: string
          maxLength: 1000
          description: Plain text only — no HTML, script tags, or dangerous characters
        client_created_at:
          type: integer
          format: int64
          description: Unix timestamp
        client_updated_at:
          type: integer
          format: int64
          description: Unix timestamp

    # ── Custom Apps ───────────────────────────────────────────────────────────
    CustomAppSummary:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64

    CustomApp:
      allOf:
        - $ref: "#/components/schemas/CustomAppSummary"
        - type: object
          properties:
            banner:
              type: object
              additionalProperties: true
            splash:
              type: object
              properties:
                active:
                  type: boolean
                image:
                  type: string
                  description: Image file UUID
                color:
                  type: string
                aspect:
                  type: string
                  enum: [fit, fill]
                created_at:
                  type: integer
                  format: int64
                updated_at:
                  type: integer
                  format: int64
            menus:
              type: array
              items:
                type: object
                properties:
                  index:
                    type: integer
                  target:
                    type: string
                  label:
                    type: object
                    additionalProperties:
                      type: string
                    description: Localised labels keyed by locale (e.g. `fr`, `en`)
                  icon:
                    type: object
                    properties:
                      type:
                        type: string
                      value:
                        type: string
                  created_at:
                    type: integer
                    format: int64
                  updated_at:
                    type: integer
                    format: int64
            screens:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                  created_at:
                    type: integer
                    format: int64
                  updated_at:
                    type: integer
                    format: int64
                  pages:
                    type: array
                    items:
                      type: object
                      properties:
                        system_name:
                          type: string
                        title:
                          type: object
                          additionalProperties:
                            type: string
                        side_menu:
                          type: boolean
                        widgets:
                          type: array
                          items:
                            type: object
                            properties:
                              type:
                                type: string
                              index:
                                type: integer
                              label:
                                type: object
                                additionalProperties:
                                  type: string
                              color:
                                type: string
                              target:
                                type: string
                              icon:
                                type: object
                                additionalProperties: true
                              background:
                                type: string
                                description: Background image UUID (full button type)
                        layout:
                          type: array
                          items:
                            type: object
                            properties:
                              x:
                                type: integer
                              y:
                                type: integer
                              width:
                                type: integer
                              height:
                                type: integer
            settings:
              type: array
              items: {}
            groups:
              type: array
              items: {}
            widget_spacing:
              type: integer
              enum: [0, 1]
              description: "`0` = flat mode, `1` = standard"
            button_type:
              type: string
              enum: [standard, mix, full]
              description: "`standard` = Font Awesome icon, `mix` = image icon, `full` = background image"
            label_overlay:
              type: boolean
            grant_all:
              type: boolean

    # ── Lists ─────────────────────────────────────────────────────────────────
    ListItem:
      type: object
      properties:
        id:
          type: integer
        root_id:
          type: integer
        parent_id:
          type: integer
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64
        deleted_at:
          type: integer
          format: int64
          nullable: true
        name:
          type: string
        has_image:
          type: boolean
        level:
          type: integer
          description: Depth — 0 is the root node
        position:
          type: integer
        external_id:
          type: string
        url:
          type: string
        functionnal_status_color:
          type: string

    ListPagedResponse:
      type: object
      properties:
        lists:
          type: array
          items:
            type: object
            properties:
              root_id:
                type: integer
              name:
                type: string
              created_at:
                type: integer
                format: int64
              list_last_updated:
                type: integer
                format: int64
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ListItemPagedResponse:
      type: object
      properties:
        lists:
          type: array
          items:
            $ref: "#/components/schemas/ListItem"
        total_count:
          type: integer
        total_pages:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ListCreateRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        external_id:
          type: string
        items:
          type: array
          description: Nested child items (recursive)
          items:
            $ref: "#/components/schemas/ListCreateRequest"

    ListItemUpdateRequest:
      type: object
      properties:
        name:
          type: string
        latitude:
          type: number
          format: float
        longitude:
          type: number
          format: float
        external_id:
          type: string
        functionnal_status_color:
          type: string
        url:
          type: string

    ListCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
            nbRecordsTodo:
              type: integer
            nbRecordsDone:
              type: integer
            nbSuccess:
              type: integer
            nbErrors:
              type: integer
            success:
              type: array
              items: {}
            errors:
              type: array
              items:
                type: string

    # ── List Access Sets ──────────────────────────────────────────────────────
    ListAccessSetSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        users:
          type: array
          items:
            type: string
        groups:
          type: array
          items: {}
        root_id:
          type: integer
        lists:
          type: array
          items:
            type: integer

    ListAccessSetDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        users:
          type: array
          items:
            $ref: "#/components/schemas/UserRef"
        groups:
          type: array
          items: {}
        root_id:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            level:
              type: integer
            position:
              type: integer
            has_image:
              type: boolean
        lists:
          type: array
          items:
            $ref: "#/components/schemas/ListItem"

    # ── Tasks ─────────────────────────────────────────────────────────────────
    Task:
      type: object
      properties:
        id:
          type: string
          format: uuid
        created_at:
          type: integer
          format: int64
        updated_at:
          type: integer
          format: int64
        start_date:
          type: integer
          format: int64
          description: Planned start date (Unix timestamp)
        due_date:
          type: integer
          format: int64
          description: Due date (Unix timestamp)
        duration:
          type: integer
          description: Expected duration in seconds
        delay_before:
          type: integer
          description: Indicative display delay in seconds before the task begins
        delay_after:
          type: integer
          description: Indicative display delay in seconds after task completion time
        user_id:
          type: integer
        submission:
          $ref: "#/components/schemas/Submission"

    TaskListResponse:
      type: object
      properties:
        tasks:
          type: array
          items:
            $ref: "#/components/schemas/Task"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    # ── Tasks (creation) ────────────────────────────────────────────────────────
    TaskInput:
      type: object
      required: [start_date]
      description: |
        Body for creating a task. Provide either `submission_model_id` or `submissions`.
        Add `recurrency` for a recurring task (in which case `due_date` is optional).
      properties:
        start_date:
          type: integer
          format: int64
          description: Task start date (UNIX timestamp)
        due_date:
          type: integer
          format: int64
          description: Due date (UNIX timestamp). Optional for a recurring task.
        submission_model_id:
          type: string
          format: uuid
          description: Identifier of a submission model
        submissions:
          type: array
          description: List of submission identifiers
          items:
            type: string
            format: uuid
        user_id:
          type: integer
          nullable: true
          description: Identifier of the assigned user
        duration:
          type: integer
          description: Task duration in seconds
        delay_before:
          type: integer
          description: Indicative display delay in seconds before the task begins
        delay_after:
          type: integer
          description: Indicative display delay in seconds after the task's due time
        recurrency:
          $ref: "#/components/schemas/Recurrency"

    Recurrency:
      type: object
      description: Recurrence definition for a task
      properties:
        type:
          type: string
          enum: [daily, weekly]
          description: Recurrence type
        interval:
          type: integer
          description: Interval between each occurrence (minimum 1)
        days:
          type: object
          description: |
            Days of the week (first 3 letters in English: mon, tue, wed, thu, fri, sat, sun).
            For weekly recurrence, not all days are required.
          additionalProperties:
            type: object
            properties:
              start:
                type: string
                description: Start time (HH:MM format)
              end:
                type: integer
                description: Task duration in seconds
        end:
          type: object
          description: Recurrence end condition
          properties:
            type:
              type: string
              enum: [occurrence, date]
              description: "`occurrence` = number of occurrences, `date` = end date"
            value:
              type: integer
              description: Number of occurrences or end date (timestamp) depending on `type`

    # ── Mail Merge ────────────────────────────────────────────────────────────────
    ReportTemplate:
      type: object
      description: Mail-merge template
      properties:
        id:
          type: integer
          description: Template identifier
        name:
          type: string
          description: Template name
        listingMode:
          type: boolean
          description: Whether the template is in listing mode
        templateEngine:
          type: string
          description: Template generation engine (e.g. docxopenxml, xlsxaspose)

    ReportTemplateListResponse:
      type: object
      properties:
        templates:
          type: array
          items:
            $ref: "#/components/schemas/ReportTemplate"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    Report:
      type: object
      description: Generated mail merge
      properties:
        id:
          type: integer
          description: Mail merge identifier
        status:
          type: string
          description: Mail merge status (e.g. finished, error)
        original_document:
          type: object
          description: File generated in the original format
          properties:
            file_uuid:
              type: string
              description: UUID of the generated file
            file_name:
              type: string
              description: Name of the generated file
        pdf_document:
          type: object
          description: File generated in PDF format
          properties:
            file_uuid:
              type: string
            file_name:
              type: string
        errorMessage:
          type: string
          description: Error message, if any

    ReportListResponse:
      type: object
      properties:
        reports:
          type: array
          items:
            $ref: "#/components/schemas/Report"
        total_count:
          type: integer
        next_page:
          type: integer
        server_time:
          type: integer
          format: int64

    ReportInput:
      type: object
      required: [template_id, submission_id, api_callback]
      description: Body for starting a mail merge
      properties:
        template_id:
          type: integer
          description: Template identifier (mandatory)
        submission_id:
          type: string
          format: uuid
          description: Submission identifier (mandatory)
        api_callback:
          type: string
          description: "URL called (PUT) when generation completes; `{publipostage_id}` is substituted (mandatory)"
        header_name:
          type: string
          description: HTTP header name to send during the callback (optional)
        header_value:
          type: string
          description: HTTP header value to send during the callback (optional)
        join_format:
          type: string
          enum: [original, pdf, "original,pdf"]
          description: "Output format: `original`, `pdf`, or `original,pdf` (two reports generated)"

    # ── Subscriptions ───────────────────────────────────────────────────────────
    Subscription:
      type: object
      description: Subscription attached to an account
      properties:
        start:
          type: integer
          format: int64
          description: Start date (UNIX timestamp)
        end:
          type: integer
          format: int64
          description: End date (UNIX timestamp)
        users_max:
          type: object
          description: Maximum number of web and mobile users
          properties:
            web:
              type: integer
            mobile:
              type: integer
        id:
          type: string
          format: uuid
          description: Subscription identifier
        name:
          type: string
          description: Displayed name
        sequence:
          type: integer
          description: Sequential number
        active:
          type: boolean
          description: Subscription status
        used_licenses:
          type: object
          description: Number of licenses already in use
          properties:
            web:
              type: integer
            mobile:
              type: integer

    SubscriptionListResponse:
      type: object
      properties:
        subscriptions:
          type: array
          items:
            $ref: "#/components/schemas/Subscription"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    # ── Groups ────────────────────────────────────────────────────────────────────
    Group:
      type: object
      description: Group within a workspace
      properties:
        is_system:
          type: boolean
          description: Whether this is a system group
        id:
          type: integer
          description: Group identifier
        name:
          type: string
          description: Displayed name
        description:
          type: string
          description: Description set by the administrator
        user_ids:
          type: array
          description: Identifiers of users in the group
          items:
            type: integer
        structure_ids:
          type: array
          description: Identifiers of forms linked to the group
          items:
            type: integer

    GroupInput:
      type: object
      required: [name, description]
      description: Body for creating or updating a group
      properties:
        name:
          type: string
          description: Group name
        description:
          type: string
          description: Group description
        user_ids:
          type: array
          description: Identifiers of users in the group
          items:
            type: integer
        structure_ids:
          type: array
          description: Identifiers of forms in the group
          items:
            type: integer

    # ── Users ─────────────────────────────────────────────────────────────────────
    User:
      type: object
      description: User (object returned by GET APIs)
      properties:
        id:
          type: integer
          description: Unique system-generated identifier
        username:
          type: string
          format: email
          description: Email address (login), must be unique
        active:
          type: boolean
          description: Status (active / inactive)
        first_name:
          type: string
          description: First name
        last_name:
          type: string
          description: Last name
        subscription_id:
          type: string
          description: UUID of the attached subscription
        system_groups:
          type: array
          description: "System groups: 1 = administrator, 2 = mobile access, 3 = web access"
          items:
            type: integer
        groups:
          type: array
          description: Identifiers of the user's groups
          items:
            type: integer
        settings:
          type: object
          description: User settings (e.g. locale)
          additionalProperties: true

    UserInput:
      type: object
      required: [username, system_groups]
      description: |
        Body for creating or updating a user. Provide `plainPassword` for Daxium
        authentication, or `ssoProvider` for SSO authentication.
      properties:
        username:
          type: string
          format: email
          description: Email address (login), must be unique
        active:
          type: boolean
          description: Status (active / inactive)
        firstName:
          type: string
          description: First name
        lastName:
          type: string
          description: Last name
        locale:
          type: string
          enum: [en, fr]
          description: User language
        subscription_id:
          type: string
          description: Subscription UUID (see the `/subscriptions` API)
        system_groups:
          type: array
          description: "System groups: 1 = administrator, 2 = mobile access, 3 = web access"
          items:
            type: integer
        groups:
          type: array
          description: Group identifiers (see the `/{app_short}/groups` API)
          items:
            type: integer
        plainPassword:
          type: object
          description: |
            Password. The `first` and `second` keys are mandatory and must be identical.
            Rules: at least 1 uppercase, 1 lowercase, 1 digit, 8 characters total.
          properties:
            first:
              type: string
              format: password
            second:
              type: string
              format: password
        force_login_password:
          type: integer
          enum: [0, 1]
          description: "[SSO companies only] 0 to deactivate, 1 to force Daxium login/password authentication"
        ssoProvider:
          type: integer
          nullable: true
          description: "[SSO companies only] Identifier of the SSO provider to use (provided by Daxium)"

    UserListResponse:
      type: object
      properties:
        users:
          type: array
          items:
            $ref: "#/components/schemas/User"
        total_count:
          type: integer
        total_pages:
          type: integer
        server_time:
          type: integer
          format: int64

    Company:
      type: object
      description: Company account of the current user
      properties:
        created_at:
          type: integer
          format: int64
          description: Creation date (UNIX timestamp)
        updated_at:
          type: integer
          format: int64
          description: Last update date (UNIX timestamp)
        id:
          type: integer
          description: Identifier
        title:
          type: string
          description: Displayed name

    CurrentUserResponse:
      type: object
      properties:
        user:
          $ref: "#/components/schemas/User"
        company:
          $ref: "#/components/schemas/Company"

    PushKeyInput:
      type: object
      description: Push notification key for a device
      properties:
        id:
          type: string
          description: Unique device identifier
        key:
          type: string
          description: Push key
        os:
          type: string
          description: Device OS (Android or iOS)
        os_version:
          type: string
          description: OS version
        app_version:
          type: string
          description: Daxium-Air version
        name:
          type: string
          description: Device name

    UsersCallbackResponse:
      type: object
      properties:
        callbackDetails:
          type: object
          properties:
            status:
              type: string
              enum:
                - Planned
                - Running
                - Finished
                - Finished with errors
                - Failed
                - In timeout
              description: Current job status
            nbRecordsTodo:
              type: integer
              description: Number of users to process
            nbRecordsDone:
              type: integer
              description: Number of users already processed
            nbSuccess:
              type: integer
              description: Number of users processed successfully
            nbErrors:
              type: integer
              description: Number of users that failed
            success:
              type: array
              description: "Users created/updated successfully. Format: {\"<username>\": {\"id\": <user_id>}}"
              items:
                type: object
                additionalProperties: true
            errors:
              type: array
              description: "Errors per user. Format: {\"<username>\": {\"<field>\": [\"<error>\"]}}"
              items:
                type: object
                additionalProperties: true

    # ── Views ─────────────────────────────────────────────────────────────────────
    View:
      type: object
      description: Configured submission view
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier
        name:
          type: string
          description: Localised name of the view
        icon:
          type: object
          description: Icon type and value
          properties:
            type:
              type: string
            value:
              type: string
        view_mode:
          type: string
          description: View display mode (e.g. table)
        view_options:
          type: object
          description: View options
          properties:
            ordered_fields:
              type: array
              description: Ordered list of fields to display (applicable if view_mode = table)
              items:
                type: string
        assigned_to_user:
          type: boolean
          nullable: true
          description: Whether the view restricts submissions to those assigned to the current user
        filters:
          type: array
          description: Filtering rules for the view
          items:
            type: object
            properties:
              structure_id:
                type: integer
                description: Form identifier of the submissions to display
              item_filters:
                type: array
                description: Filtering rules on fields
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: System name of the field
                    type:
                      type: string
                      description: Daxium-Air type of the field
                    operator:
                      type: string
                      description: Operator to apply
                    values:
                      type: array
                      description: Values to compare the field value against via the operator
                      items: {}
              metadata_filters:
                type: array
                description: Filtering rules on metadata
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: System name of the metadata
                    operator:
                      type: string
                      description: Operator to apply
                    values:
                      type: array
                      items: {}

# ─────────────────────────── GLOBAL SECURITY ─────────────────────────────────
security:
  - BearerAuth: []
