openapi: 3.1.0
info:
  title: droviat Public Automation API
  description: |-
    Manage campaigns, links, QR assets, governed UTM helpers, analytics, and conversion workflows from the API surface behind Droviat's link management platform.

    This API is designed for API-key authenticated workspace automation across campaign setup, campaign-scoped link creation, governed UTM guidance, attribution readiness, conversion ingestion, and reporting workflows.

    - Create, update, list, and archive campaigns
    - Create, update, list, and delete managed links
    - Read effective QR configuration and download QR assets
    - Retrieve governed UTM suggestions, templates, presets, and context defaults
    - List conversion trigger keys before sending attribution events
    - Retrieve analytics summaries for workspace-level reporting
  version: 1.9.0

servers:
  - url: https://api.droviat.com/v1/public
    description: Public API v1 (production)

paths:
  /workspaces/{workspaceId}/links:
    get:
      operationId: publicListLinks
      summary: List links
      tags: [Public Links]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        '200':
          description: Link list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkDTO'
                  total: { type: integer }
                  page: { type: integer }
                  pageSize: { type: integer }
                  totalPages: { type: integer }
        '401':
          description: Invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
    post:
      operationId: publicCreateLink
      summary: Create link
      tags: [Public Links]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateLinkInput'
      responses:
        '201':
          description: Link created
          content:
            application/json:
              schema:
                type: object
                properties:
                  link:
                    $ref: '#/components/schemas/LinkDTO'
        '400':
          description: Validation error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
        '403':
          description: Missing required scope
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }

  /workspaces/{workspaceId}/links/{linkId}:
    get:
      operationId: publicGetLink
      summary: Get link by id
      tags: [Public Links]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/LinkId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Link
          content:
            application/json:
              schema:
                type: object
                properties:
                  link:
                    $ref: '#/components/schemas/LinkDTO'
        '404':
          description: Not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
    patch:
      operationId: publicUpdateLink
      summary: Update link
      tags: [Public Links]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/LinkId'
        - $ref: '#/components/parameters/RequestId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateLinkInput'
      responses:
        '200':
          description: Link updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  link:
                    $ref: '#/components/schemas/LinkDTO'
        '400':
          description: Validation error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
    delete:
      operationId: publicDeleteLink
      summary: Delete link
      tags: [Public Links]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/LinkId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '204':
          description: Link deleted
        '404':
          description: Link not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }

  /workspaces/{workspaceId}/links/{linkId}/qr:
    get:
      operationId: publicGetQr
      summary: Get effective QR config for link
      tags: [Public QR]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/LinkId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: QR configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  qr:
                    $ref: '#/components/schemas/QrDTO'
                  customized:
                    type: boolean

  /workspaces/{workspaceId}/links/{linkId}/qr/download:
    get:
      operationId: publicDownloadQr
      summary: Download QR asset
      tags: [Public QR]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/LinkId'
        - $ref: '#/components/parameters/RequestId'
        - name: format
          in: query
          schema:
            type: string
            enum: [png, svg]
            default: png
      responses:
        '200':
          description: QR binary file
          content:
            image/png: {}
            image/svg+xml: {}

  /workspaces/{workspaceId}/campaigns:
    get:
      operationId: publicListCampaigns
      summary: List campaigns
      description: Requires an API key with the `links:read` scope.
      tags: [Public Campaigns]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: sort
          in: query
          schema: { type: string, default: created_at }
        - name: order
          in: query
          schema: { type: string, enum: [asc, desc], default: desc }
        - name: search
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [draft, scheduled, active, completed, archived] }
      responses:
        '200':
          description: Campaign list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicCampaignListResponse'
    post:
      operationId: publicCreateCampaign
      summary: Create campaign
      description: Requires an API key with the `links:write` scope.
      tags: [Public Campaigns]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCampaignInput'
      responses:
        '201':
          description: Campaign created
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaign:
                    $ref: '#/components/schemas/CampaignDTO'
        '403':
          description: Missing required scope
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }

  /workspaces/{workspaceId}/campaigns/{campaignId}:
    get:
      operationId: publicGetCampaign
      summary: Get campaign by id
      description: Requires an API key with the `links:read` scope.
      tags: [Public Campaigns]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/CampaignId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Campaign
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaign:
                    $ref: '#/components/schemas/CampaignDTO'
        '404':
          description: Not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
    patch:
      operationId: publicUpdateCampaign
      summary: Update campaign
      description: Requires an API key with the `links:write` scope.
      tags: [Public Campaigns]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/CampaignId'
        - $ref: '#/components/parameters/RequestId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCampaignInput'
      responses:
        '200':
          description: Campaign updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaign:
                    $ref: '#/components/schemas/CampaignDTO'
    delete:
      operationId: publicArchiveCampaign
      summary: Archive campaign
      description: Requires an API key with the `links:write` scope.
      tags: [Public Campaigns]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/CampaignId'
        - $ref: '#/components/parameters/RequestId'
        - name: force
          in: query
          schema: { type: boolean, default: false }
      responses:
        '200':
          description: Campaign archived or deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaign:
                    $ref: '#/components/schemas/CampaignDTO'
        '204':
          description: Campaign deleted without a body

  /workspaces/{workspaceId}/utm-governance/suggestions:
    get:
      operationId: publicGetUtmSuggestions
      summary: Get governed UTM suggestions
      description: Requires an API key with the `links:read` scope.
      tags: [Public UTM Guidance]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
        - name: field
          in: query
          required: true
          schema:
            $ref: '#/components/schemas/UtmGovernanceField'
        - name: typedValue
          in: query
          required: true
          schema: { type: string }
        - name: authoringScope
          in: query
          schema:
            $ref: '#/components/schemas/UtmAuthoringScope'
        - name: contextType
          in: query
          schema: { type: [string, "null"] }
      responses:
        '200':
          description: Suggestion payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UtmSuggestionResponse'

  /workspaces/{workspaceId}/utm-governance/templates:
    get:
      operationId: publicListUtmTemplates
      summary: List UTM templates
      description: Requires an API key with the `links:read` scope.
      tags: [Public UTM Guidance]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Template list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UtmTemplatesResponse'

  /workspaces/{workspaceId}/utm-governance/presets:
    get:
      operationId: publicListUtmPresets
      summary: List UTM presets
      description: Requires an API key with the `links:read` scope.
      tags: [Public UTM Guidance]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Preset list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UtmPresetsResponse'

  /workspaces/{workspaceId}/utm-governance/context-defaults:
    get:
      operationId: publicListUtmContextDefaults
      summary: List UTM context defaults
      description: Requires an API key with the `links:read` scope.
      tags: [Public UTM Guidance]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
        - name: authoringScope
          in: query
          schema:
            $ref: '#/components/schemas/UtmAuthoringScope'
      responses:
        '200':
          description: Context default list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UtmContextDefaultsResponse'

  /workspaces/{workspaceId}/analytics/summary:
    get:
      operationId: publicGetAnalyticsSummary
      summary: Get analytics summary (paginated breakdown)
      tags: [Public Analytics]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 20 }
        - name: from
          in: query
          schema: { type: string, format: date-time }
        - name: to
          in: query
          schema: { type: string, format: date-time }
        - name: campaignId
          in: query
          schema: { type: string, format: uuid }
        - name: linkSearch
          in: query
          schema: { type: string }
        - name: countryCode
          in: query
          schema: { type: string }
        - name: deviceType
          in: query
          schema: { type: string }
        - name: referrerHost
          in: query
          schema: { type: string }
        - name: qrId
          in: query
          schema: { type: string, format: uuid }
        - name: timeseriesBreakdown
          in: query
          schema: { type: string, enum: [none, campaign, link], default: none }
        - name: granularity
          in: query
          schema: { type: string, enum: [minute, hour, day], default: hour }
        - name: timezone
          in: query
          schema: { type: string, default: UTC }
        - name: includeBots
          in: query
          schema: { type: boolean, default: false }
      responses:
        '200':
          description: Analytics summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAnalyticsResponse'

  /workspaces/{workspaceId}/analytics/campaigns/{campaignId}/drilldown:
    get:
      operationId: publicGetCampaignDrilldown
      summary: Get campaign analytics drilldown (paginated breakdown)
      tags: [Public Analytics]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/CampaignId'
        - $ref: '#/components/parameters/RequestId'
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 20 }
        - name: from
          in: query
          schema: { type: string, format: date-time }
        - name: to
          in: query
          schema: { type: string, format: date-time }
        - name: linkSearch
          in: query
          schema: { type: string }
        - name: countryCode
          in: query
          schema: { type: string }
        - name: deviceType
          in: query
          schema: { type: string }
        - name: referrerHost
          in: query
          schema: { type: string }
        - name: qrId
          in: query
          schema: { type: string, format: uuid }
        - name: timeseriesBreakdown
          in: query
          schema: { type: string, enum: [none, campaign, link], default: link }
        - name: granularity
          in: query
          schema: { type: string, enum: [minute, hour, day], default: hour }
        - name: timezone
          in: query
          schema: { type: string, default: UTC }
        - name: includeBots
          in: query
          schema: { type: boolean, default: false }
      responses:
        '200':
          description: Campaign drilldown
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAnalyticsResponse'

  /workspaces/{workspaceId}/attribution/triggers:
    get:
      operationId: publicListConversionTriggers
      summary: List conversion triggers
      description: Requires an API key with the `conversions:write` scope.
      tags: [Public Attribution]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Conversion trigger list plus readiness guidance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicConversionTriggersResponse'

  /workspaces/{workspaceId}/conversions:
    post:
      operationId: publicIngestConversion
      summary: Register a conversion event for attribution processing
      description: Requires an API key with the `conversions:write` scope.
      tags: [Public Attribution]
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/RequestId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IngestConversionInput'
      responses:
        '202':
          description: Conversion accepted for asynchronous attribution processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedConversionResponse'
        '403':
          description: Missing required scope
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
        '404':
          description: Conversion trigger not found
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }
        '422':
          description: Validation error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PublicErrorResponse' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

  parameters:
    RequestId:
      name: x-request-id
      in: header
      required: false
      schema:
        type: string
    WorkspaceId:
      name: workspaceId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    LinkId:
      name: linkId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    CampaignId:
      name: campaignId
      in: path
      required: true
      schema:
        type: string
        format: uuid

  schemas:
    PublicErrorResponse:
      type: object
      required: [error, requestId]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
            details:
              type: array
              items:
                type: string
        requestId:
          type: string

    CreateLinkInput:
      type: object
      required: [destinationUrl]
      properties:
        domainId: { type: [string, "null"], format: uuid }
        slug: { type: [string, "null"] }
        destinationUrl: { type: string }
        title: { type: [string, "null"] }
        redirectCode: { type: integer, enum: [301, 302], default: 302 }
        expiresAt: { type: [string, "null"], format: date-time }
        expirationBehavior: { type: string, enum: [gone_410, redirect, not_found_404], default: gone_410 }
        expirationRedirectUrl: { type: [string, "null"] }
        campaignId: { type: [string, "null"], format: uuid }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }
        tags:
          type: array
          items: { type: string }

    UpdateLinkInput:
      type: object
      properties:
        destinationUrl: { type: string }
        title: { type: [string, "null"] }
        redirectCode: { type: integer, enum: [301, 302] }
        isActive: { type: boolean }
        expiresAt: { type: [string, "null"], format: date-time }
        expirationBehavior: { type: string, enum: [gone_410, redirect, not_found_404] }
        expirationRedirectUrl: { type: [string, "null"] }
        campaignId: { type: [string, "null"], format: uuid }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }
        tags:
          type: array
          items: { type: string }

    LinkDTO:
      type: object
      properties:
        id: { type: string, format: uuid }
        workspaceId: { type: string, format: uuid }
        domainId: { type: [string, "null"], format: uuid }
        shortUrl: { type: string }
        slug: { type: string }
        destinationUrl: { type: string }
        title: { type: [string, "null"] }
        redirectCode: { type: integer }
        isActive: { type: boolean }
        expiresAt: { type: [string, "null"], format: date-time }
        expirationBehavior: { type: string }
        expirationRedirectUrl: { type: [string, "null"] }
        campaignId: { type: [string, "null"], format: uuid }
        tags:
          type: array
          items: { type: string }
        clickCount: { type: integer }
        scanCount: { type: integer }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    CampaignDTO:
      type: object
      properties:
        id: { type: string, format: uuid }
        workspaceId: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }
        description: { type: [string, "null"] }
        status: { type: string, enum: [draft, scheduled, active, completed, archived] }
        startsAt: { type: [string, "null"], format: date-time }
        endsAt: { type: [string, "null"], format: date-time }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }
        createdByMemberId: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
        archivedAt: { type: [string, "null"], format: date-time }

    PublicCampaignListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CampaignDTO'
        page: { type: integer }
        pageSize: { type: integer }
        total: { type: integer }
        totalPages: { type: integer }

    CreateCampaignInput:
      type: object
      required: [name, slug]
      properties:
        name: { type: string, maxLength: 120 }
        slug: { type: string, maxLength: 80 }
        description: { type: [string, "null"], maxLength: 1000 }
        status: { type: string, enum: [draft, scheduled, active], default: draft }
        startsAt: { type: [string, "null"], format: date-time }
        endsAt: { type: [string, "null"], format: date-time }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }

    UpdateCampaignInput:
      type: object
      properties:
        name: { type: string, maxLength: 120 }
        slug: { type: string, maxLength: 80 }
        description: { type: [string, "null"], maxLength: 1000 }
        status: { type: string, enum: [draft, scheduled, active, completed, archived] }
        startsAt: { type: [string, "null"], format: date-time }
        endsAt: { type: [string, "null"], format: date-time }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }

    QrDTO:
      type: object
      properties:
        id: { type: string }
        linkId: { type: string, format: uuid }
        campaignId: { type: [string, "null"], format: uuid }
        payloadUrl: { type: string }
        foregroundColor: { type: string }
        backgroundColor: { type: string }
        logoAssetUrl: { type: [string, "null"] }
        logoScalePercent: { type: integer }
        errorCorrectionLevel: { type: string, enum: [L, M, Q, H] }
        margin: { type: integer }
        sizePx: { type: integer }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    PublicAnalyticsResponse:
      type: object
      properties:
        query:
          type: object
          additionalProperties: true
        totals:
          type: object
          additionalProperties: true
        workspaceKpis:
          type: object
          additionalProperties: true
        timeseries:
          type: array
          items:
            type: object
            additionalProperties: true
        timeseriesBreakdown:
          type: array
          items:
            type: object
            additionalProperties: true
        breakdown:
          type: object
          additionalProperties: true
        page: { type: integer }
        pageSize: { type: integer }
        total: { type: integer }
        totalPages: { type: integer }

    UtmGovernanceField:
      type: string
      enum: [utm_source, utm_medium, utm_campaign, utm_term, utm_content]

    UtmAuthoringScope:
      type: string
      enum: [campaign, link]

    UtmHistorySuggestion:
      type: object
      properties:
        id: { type: string, format: uuid }
        value: { type: string }
        usageCount: { type: integer }
        lastUsedAt: { type: string, format: date-time }

    UtmGuidedFieldRecommendation:
      type: object
      properties:
        field:
          $ref: '#/components/schemas/UtmGovernanceField'
        suggestedValue: { type: [string, "null"] }
        rationale: { type: [string, "null"] }
        source:
          type: string
          enum: [history, preset, template, industry, none]

    UtmGovernanceTemplate:
      type: object
      properties:
        id: { type: string, format: uuid }
        assetType: { type: string, enum: [template] }
        workspaceId: { type: string, format: uuid }
        name: { type: string }
        description: { type: [string, "null"] }
        status: { type: string, enum: [draft, active, archived] }
        contextType: { type: [string, "null"] }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }
        createdByMemberId: { type: [string, "null"] }
        updatedByMemberId: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    UtmGovernancePreset:
      type: object
      properties:
        id: { type: string, format: uuid }
        assetType: { type: string, enum: [preset] }
        scope: { type: string, enum: [platform, workspace] }
        workspaceId: { type: [string, "null"], format: uuid }
        name: { type: string }
        description: { type: [string, "null"] }
        status: { type: string, enum: [draft, active, archived] }
        contextType: { type: [string, "null"] }
        utmSource: { type: [string, "null"] }
        utmMedium: { type: [string, "null"] }
        utmCampaign: { type: [string, "null"] }
        utmTerm: { type: [string, "null"] }
        utmContent: { type: [string, "null"] }
        isSystemSeed: { type: boolean }
        createdByMemberId: { type: [string, "null"] }
        updatedByMemberId: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    UtmContextDefault:
      type: object
      properties:
        id: { type: string, format: uuid }
        workspaceId: { type: string, format: uuid }
        contextType: { type: string }
        authoringScope:
          $ref: '#/components/schemas/UtmAuthoringScope'
        assetType: { type: string, enum: [template, preset] }
        assetId: { type: string, format: uuid }
        status: { type: string, enum: [draft, active, archived] }
        updatedByMemberId: { type: [string, "null"] }
        updatedAt: { type: string, format: date-time }
        createdAt: { type: string, format: date-time }
        asset:
          oneOf:
            - $ref: '#/components/schemas/UtmGovernanceTemplate'
            - $ref: '#/components/schemas/UtmGovernancePreset'
            - type: "null"

    UtmSuggestionResponse:
      type: object
      properties:
        field:
          $ref: '#/components/schemas/UtmGovernanceField'
        typedValue: { type: string }
        exactMatches:
          type: array
          items:
            $ref: '#/components/schemas/UtmHistorySuggestion'
        closeMatches:
          type: array
          items:
            $ref: '#/components/schemas/UtmHistorySuggestion'
        recommendedTemplates:
          type: array
          items:
            $ref: '#/components/schemas/UtmGovernanceTemplate'
        recommendedPresets:
          type: array
          items:
            $ref: '#/components/schemas/UtmGovernancePreset'
        contextDefaults:
          type: array
          items:
            $ref: '#/components/schemas/UtmContextDefault'
        guidedRecommendation:
          $ref: '#/components/schemas/UtmGuidedFieldRecommendation'

    UtmTemplatesResponse:
      type: object
      properties:
        templates:
          type: array
          items:
            $ref: '#/components/schemas/UtmGovernanceTemplate'

    UtmPresetsResponse:
      type: object
      properties:
        presets:
          type: array
          items:
            $ref: '#/components/schemas/UtmGovernancePreset'

    UtmContextDefaultsResponse:
      type: object
      properties:
        contextDefaults:
          type: array
          items:
            $ref: '#/components/schemas/UtmContextDefault'

    ConversionTriggerDTO:
      type: object
      properties:
        id: { type: string, format: uuid }
        workspaceId: { type: string, format: uuid }
        key: { type: string }
        name: { type: string }
        description: { type: [string, "null"] }
        source:
          type: string
          enum: [builtin, custom]
        attributionWindowDays: { type: integer, minimum: 1, maximum: 365 }
        isArchived: { type: boolean }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    PublicConversionTriggersResponse:
      type: object
      properties:
        triggers:
          type: array
          items:
            $ref: '#/components/schemas/ConversionTriggerDTO'
        readinessGuidance:
          type: string

    IngestConversionInput:
      type: object
      required: [triggerKey]
      properties:
        ingestionSource:
          type: string
          enum: [browser, server]
          default: server
        triggerKey:
          type: string
          description: Workspace-scoped conversion trigger key.
        clickId:
          type: [string, "null"]
          description: Explicit canonical click identifier supplied by the caller.
        recoveredClickId:
          type: [string, "null"]
          description: Recovered click identifier from a browser helper or first-party store.
        orderId:
          type: [string, "null"]
        externalRef:
          type: [string, "null"]
        revenueAmount:
          oneOf:
            - type: number
            - type: string
            - type: "null"
        currency:
          type: [string, "null"]
          description: Three-letter ISO currency code.
        occurredAt:
          type: [string, "null"]
          format: date-time
        metadata:
          type: object
          additionalProperties: true

    AcceptedConversionResponse:
      type: object
      properties:
        conversion:
          type: object
          properties:
            id: { type: string, format: uuid }
            status:
              type: string
              enum: [received, attributed, unattributed, duplicate_ignored]
            triggerId: { type: string, format: uuid }
            triggerKey: { type: string }
            clickContextSource:
              type: string
              enum: [explicit, recovered, none]
            canonicalClickId: { type: [string, "null"] }
            occurredAt: { type: string, format: date-time }
            acceptedAt: { type: string, format: date-time }
