openapi: 3.0.3
info:
  title: Maecos Public API — Checklists
  description: |
    Public API endpoint for programmatically creating (instancing) checklists from templates,
    optionally pre-filling measurement values, and registering webhook callbacks.

    All requests are authenticated via the `X-Api-Key` header. API keys are either
    tenant-level (static config) or per-key (BCrypt-verified, stored in the database).
  version: 1.0.0

servers:
  - url: /api
    description: Public API base path

security:
  - apiKey: []

paths:
  /checklists:
    post:
      operationId: createChecklists
      summary: Create checklist instances from a template
      description: |
        Instances one or more checklists from a checklist template definition. A checklist
        instance is created for each **location x workstation** combination resolved from
        the template and the (optionally filtered) location/procedure lists.

        Optionally, measurement values can be pre-filled on procedures and webhook callbacks
        can be registered to fire when the checklists are closed.

        All events (instancing, measurements, webhooks) are persisted in a single
        transactional batch — if any measurement fails, nothing is persisted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChecklistsRequest'
            examples:
              minimal:
                summary: Minimal request — instance all locations/procedures
                value:
                  checklistTemplateId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              withNumericTemplateId:
                summary: Reference template by numeric ID
                value:
                  checklistTemplateId: 42
              full:
                summary: Full request with measurements and callbacks
                value:
                  title: "Shift A Morning Check"
                  checklistTemplateId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  locationIds:
                    - "11111111-1111-1111-1111-111111111111"
                  procedureIds:
                    - "22222222-2222-2222-2222-222222222222"
                    - "33333333-3333-3333-3333-333333333333"
                  earliestStartAt: "2024-06-01T06:00:00+02:00"
                  closeAt: "2024-06-01T14:00:00+02:00"
                  data:
                    - procedureId: "22222222-2222-2222-2222-222222222222"
                      value: "7.5"
                    - procedureId: "33333333-3333-3333-3333-333333333333"
                      value: "OK"
                  callbacks:
                    - type: "checklist.closed"
                      method: "POST"
                      uri: "https://example.com/webhook/checklist-closed"
                      headers:
                        - name: "Authorization"
                          value: "Bearer my-token"
      responses:
        '201':
          description: One or more checklists were successfully created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateChecklistsResponse'
              example:
                totalCount: 2
                checklist.started:
                  - "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
                  - "ffffffff-1111-2222-3333-444444444444"
        '204':
          description: >
            Request was valid but no checklists were created (e.g. no matching
            location/procedure combinations resolved from the template).
        '400':
          description: >
            Validation error. Either the JSON could not be parsed, request
            parameters failed validation, the checklist template was not found,
            or one or more measurements could not be added.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ValidationError'
                  - $ref: '#/components/schemas/ParseError'
              examples:
                invalidChecklistTemplateId:
                  summary: Invalid checklist template ID
                  value:
                    title: "Your request parameters didn't validate."
                    invalidParams:
                      - path: "$.checklistTemplateId"
                        code: "guid.invalid-missing"
                        details: "https://en.wikipedia.org/wiki/Universally_unique_identifier#Format"
                templateNotFound:
                  summary: Checklist template not found
                  value:
                    title: "Your request parameters didn't validate."
                    invalidParams:
                      - path: "$.checklistTemplateId"
                        code: "not-found"
                        details: "Could not create checklist because the system could not find checklist template by specified id or the checklist template has no locations defined or the checklist template is empty."
                invalidTimestamp:
                  summary: Invalid RFC3339 timestamp
                  value:
                    title: "Your request parameters didn't validate."
                    invalidParams:
                      - path: "$.earliestStartAt"
                        code: "timestamp.invalid-format"
                        details: "https://datatracker.ietf.org/doc/html/rfc3339"
                multipleErrors:
                  summary: Multiple validation errors collected
                  value:
                    title: "Your request parameters didn't validate."
                    invalidParams:
                      - path: "$.locationIds.[2]"
                        code: "guid.invalid-missing"
                        details: "https://en.wikipedia.org/wiki/Universally_unique_identifier#Format"
                      - path: "$.closeAt"
                        code: "timestamp.invalid-format"
                        details: "https://datatracker.ietf.org/doc/html/rfc3339"
                measurementFailed:
                  summary: Measurement could not be added
                  value:
                    title: "Your request parameters didn't validate."
                    invalidParams:
                      - path: "$.data.[0]"
                        code: "measurement.failed"
                        details: ""
                jsonParseError:
                  summary: JSON body could not be parsed
                  value:
                    title: "Failed to parse provided json."
                    details: "Error at line 3, column 5: Unexpected token"
        '401':
          description: Authentication failed.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/AuthenticationError'
              examples:
                missingKey:
                  summary: X-Api-Key header not present
                  value:
                    title: "Your request did not authenticate."
                    detail: "The X-Api-Key header is not present."
                invalidKey:
                  summary: X-Api-Key header present but invalid
                  value:
                    title: "Your request did not authenticate."
                    detail: "The X-Api-Key header is present but does not contain a valid value."

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        Tenant-level or per-key API key. Per-key API keys are at least 40 characters
        and are verified via BCrypt. Tenant-level keys are checked against a static
        configuration list.

  schemas:
    CreateChecklistsRequest:
      type: object
      properties:
        title:
          description: |
            Custom title for the instanced checklists. If omitted, the checklist
            template's name is used. Accepts both strings and numbers (numbers are
            cast to string).
          oneOf:
            - type: string
            - type: integer
              format: uint32
          example: "Shift A Morning Check"
        checklistTemplateId:
          description: |
            Identifies the checklist template to instance. Accepts either a UUID
            string or a numeric checklist definition number.
          oneOf:
            - type: string
              format: uuid
              example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
            - type: integer
              format: uint32
              example: 42
        locationIds:
          type: array
          description: |
            UUIDs of locations to instance. If omitted, **all locations** defined
            on the checklist template are used.
          items:
            type: string
            format: uuid
          example:
            - "11111111-1111-1111-1111-111111111111"
        procedureIds:
          type: array
          description: |
            UUIDs of procedure templates to include. If omitted, **all procedures**
            from the template's ordered master list are used.
          items:
            type: string
            format: uuid
          example:
            - "22222222-2222-2222-2222-222222222222"
        earliestStartAt:
          type: string
          format: date-time
          description: |
            RFC3339 timestamp for the checklist's earliest start time. If omitted,
            defaults to the server's current time in the tenant's timezone.
          example: "2024-06-01T06:00:00+02:00"
        closeAt:
          type: string
          format: date-time
          description: |
            RFC3339 timestamp for the checklist's auto-close time. Optional.
          example: "2024-06-01T14:00:00+02:00"
        data:
          type: array
          description: |
            Pre-filled measurement values. Entries with blank/empty `value` fields
            are silently filtered out. Each entry maps a procedure template to a value.
          items:
            $ref: '#/components/schemas/CheckData'
        callbacks:
          type: array
          description: |
            Webhook callback registrations. Each callback fires when the specified
            condition is met for each instanced checklist.
          items:
            $ref: '#/components/schemas/CallbackData'

    CheckData:
      type: object
      required:
        - procedureId
        - value
      properties:
        procedureId:
          type: string
          format: uuid
          description: |
            UUID of the procedure template (the `checklist_definition_procedure_id`,
            not the procedure instance ID).
          example: "22222222-2222-2222-2222-222222222222"
        value:
          type: string
          description: |
            The measurement value as a string. The expected format depends on the
            procedure's measurement definition type:

            | Procedure type     | Format                          | Example       |
            |--------------------|---------------------------------|---------------|
            | **Textual**        | Any non-blank string            | `"OK"`        |
            | **Numeric/Decimal**| Decimal number                  | `"7.5"`       |
            | **Numeric/Integer**| Integer number                  | `"42"`        |
            | **Numeric/Fraction** | `"numerator/denominator"`     | `"3/4"`       |
            | **Choice**         | Option label or integer value   | `"Good"`, `"2"` |
            | **DateTime**       | ISO date/time string            | `"2024-06-01T08:00:00Z"` |

            Procedures requiring photo evidence are not supported via this API and
            will be rejected.
          example: "7.5"

    CallbackData:
      type: object
      required:
        - type
        - method
        - uri
      properties:
        type:
          type: string
          description: |
            The event condition that triggers the webhook. Currently only
            `"checklist.closed"` is supported (case-insensitive).
          enum:
            - checklist.closed
          example: "checklist.closed"
        method:
          type: string
          description: |
            HTTP method for the webhook request. Case-insensitive.
          enum:
            - POST
            - PUT
            - DELETE
          example: "POST"
        uri:
          type: string
          format: uri
          description: Absolute URI for the webhook endpoint.
          example: "https://example.com/webhook/checklist-closed"
        headers:
          type: array
          description: |
            Custom HTTP headers to include in the webhook request. The
            `content-type` header is silently ignored — the system always
            sends `application/json`.
          items:
            $ref: '#/components/schemas/CallbackHeader'

    CallbackHeader:
      type: object
      required:
        - name
        - value
      properties:
        name:
          type: string
          description: Header name. Must not be blank. `content-type` is silently stripped.
          example: "Authorization"
        value:
          type: string
          description: Header value. Must not be blank.
          example: "Bearer my-token"

    CreateChecklistsResponse:
      type: object
      properties:
        totalCount:
          type: integer
          description: Number of checklist instances created.
          example: 2
        checklist.started:
          type: array
          description: UUIDs of the created checklist instances.
          items:
            type: string
            format: uuid
          example:
            - "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
            - "ffffffff-1111-2222-3333-444444444444"

    ValidationError:
      type: object
      description: |
        Returned when request parameters fail validation. Errors are collected
        applicatively — multiple errors may be reported at once.
      properties:
        title:
          type: string
          example: "Your request parameters didn't validate."
        invalidParams:
          type: array
          items:
            $ref: '#/components/schemas/InvalidParam'

    InvalidParam:
      type: object
      properties:
        path:
          type: string
          description: |
            JSON path to the invalid field. Uses `$.field` notation with
            array indexes where applicable.
          example: "$.checklistTemplateId"
        code:
          type: string
          description: Machine-readable error code.
          enum:
            - guid.invalid-missing
            - timestamp.invalid-format
            - not-found
            - value.missing
            - value.invalid
            - procedure.not-found
            - procedure.requires-evidence
            - procedure.not-supported
            - procedure.execution-window-not-started
            - procedure.requires-equipment
            - measurement.failed
            - type.missing
            - type.invalid
            - method.missing
            - method.invalid
            - uri.missing
            - uri.invalid
            - name.missing
            - value.missing
          example: "guid.invalid-missing"
        details:
          type: string
          description: |
            Human-readable details or a reference link. May be an empty string.
          example: "https://en.wikipedia.org/wiki/Universally_unique_identifier#Format"

    ParseError:
      type: object
      description: Returned when the JSON body cannot be decoded.
      properties:
        title:
          type: string
          example: "Failed to parse provided json."
        details:
          type: string
          description: Decoder error message.
          example: "Error at line 3, column 5: Unexpected token"

    AuthenticationError:
      type: object
      properties:
        title:
          type: string
          example: "Your request did not authenticate."
        detail:
          type: string
          example: "The X-Api-Key header is not present."
