openapi: 3.1.0
info:
  title: Infinite Audience API
  version: 1.0.0
  license:
    name: Proprietary — All Rights Reserved
    url: https://infiniteaudience.ai/terms
  description: >
    The primary REST API for Infinite Audience.

    **Authentication:** All protected endpoints require a Bearer token in the
    `Authorization` header (`Authorization: Bearer <access_token>`). Obtain an
    access token by posting your API key to `POST /v1/auth/token`.

    **API Key Scopes:** API keys can be restricted to specific scopes. If a
    token lacks the required scope for an endpoint, a `403 Forbidden` response
    is returned with a `SCOPE_REQUIRED` error code. - `discovery` — Read-only
    access to count, lookup, segments, demographics, catalogs, and status
    polling. - `purchase` — Read-write access to create/update segments and
    audiences, link campaigns, and execute purchases/deliveries. - `account` —
    Full access to self-service API key management, webhook settings, delivery
    destinations, and billing details.
servers:
  - url: https://api.infiniteaudience.ai
    description: Production API Server
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        Bearer access token obtained from POST /v1/auth/token. Include in all
        protected requests as: Authorization: Bearer <access_token>
  schemas:
    CostEstimate:
      type: object
      required:
        - base_cost
        - match_cost
        - field_cost
        - destination_cost
        - total_cost
        - unit_price
        - billing_count
      properties:
        base_cost:
          type: number
          description: Base audience-delivery cost.
        match_cost:
          type: number
          description: One-time deferred file-match cost included only until the
            underlying run is first delivered.
        field_cost:
          type: number
          description: Total surcharge for requested attributes.
        destination_cost:
          type: number
          description: Destination surcharge (0 for download).
        total_cost:
          type: number
          description: Sum of all cost components.
        unit_price:
          type: number
          description: Effective cost per record.
        billing_count:
          type: integer
          description: Number of records that will be billed.
    LicensePricing:
      type: object
      required:
        - status
        - evaluated_at
        - already_licensed_count
      properties:
        status:
          type: string
          enum:
            - live
            - cached
            - unavailable
          description: >
            Whether cost_estimate reflects a freshly-computed license split
            (live), a recent cached one (cached, still authoritative — only the
            underlying counts are reused, dollars are always recomputed from
            current rates), or could not be determined in time (unavailable —
            cost_estimate equals max_cost_estimate exactly in this case, so no
            consumer sees a misleading number).
        evaluated_at:
          type:
            - string
            - "null"
          format: date-time
          description: >
            When the license split backing cost_estimate was computed. For a
            cached split this is the ORIGINAL computation time, not now — use it
            to judge staleness. Null when status is unavailable.
        already_licensed_count:
          type:
            - integer
            - "null"
          description: >
            How many of this audience's candidate identities already hold an
            active license elsewhere and were therefore excluded from
            cost_estimate's billable count. Null when status is unavailable.
    IntegrationRunCounts:
      type: object
      additionalProperties: false
      required:
        - input
        - matched
        - delivered
        - applied
        - unchanged
        - skipped
        - failed
      properties:
        input:
          type: integer
          minimum: 0
        matched:
          type: integer
          minimum: 0
        delivered:
          type: integer
          minimum: 0
        applied:
          type: integer
          minimum: 0
        unchanged:
          type: integer
          minimum: 0
        skipped:
          type: integer
          minimum: 0
        failed:
          type: integer
          minimum: 0
    IntegrationRun:
      type: object
      additionalProperties: false
      required:
        - run_id
        - provider
        - connection_id
        - connection_display_name
        - kind
        - data_flow
        - purpose
        - status
        - billing_source
        - customer_boundary
        - counts
        - selected_fields
        - boundary_at
        - failure_code
        - failure_message
        - created_at
        - updated_at
        - completed_at
      properties:
        run_id:
          type: string
        provider:
          type: string
        connection_id:
          type: string
        connection_display_name:
          type:
            - string
            - "null"
        kind:
          type: string
          enum:
            - batch_enrichment
            - file_enrichment
            - ingest
            - activation
            - export
        data_flow:
          type: string
          enum:
            - provider_to_platform
            - platform_to_provider
            - round_trip
        purpose:
          type: string
          enum:
            - enrichment
            - ingest
            - activation
            - export
        status:
          type: string
          enum:
            - created
            - processing
            - awaiting_boundary
            - settling
            - completed
            - failed
            - cancelled
        billing_source:
          type: string
          enum:
            - native
            - shopify
        customer_boundary:
          type: string
          enum:
            - api_response
            - file_available
            - destination_handoff
            - provider_writeback
        counts:
          $ref: "#/components/schemas/IntegrationRunCounts"
        selected_fields:
          type: array
          items:
            type: string
        boundary_at:
          type:
            - string
            - "null"
          format: date-time
        failure_code:
          type:
            - string
            - "null"
        failure_message:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time
    IntegrationRunListResponse:
      type: object
      additionalProperties: false
      required:
        - runs
        - has_more
        - next_cursor
      properties:
        runs:
          type: array
          items:
            $ref: "#/components/schemas/IntegrationRun"
        has_more:
          type: boolean
        next_cursor:
          type:
            - string
            - "null"
    IntegrationMatchCollection:
      type: object
      required:
        - collection_id
        - org_id
        - name
        - kind
        - status
        - retention_days
        - unique_identity_count
        - count_refreshed_at
        - last_captured_at
        - created_at
        - updated_at
      properties:
        collection_id:
          type: string
        org_id:
          type: string
        name:
          type: string
        kind:
          type: string
          enum:
            - default
        status:
          type: string
          enum:
            - active
            - paused
        retention_days:
          type: integer
          enum:
            - 90
        unique_identity_count:
          type:
            - integer
            - "null"
          minimum: 0
        count_refreshed_at:
          type:
            - string
            - "null"
          format: date-time
        last_captured_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    IntegrationCollectionFilter:
      type: object
      required: []
      additionalProperties: false
      properties:
        providers:
          type: array
          maxItems: 20
          items:
            type: string
        connection_ids:
          type: array
          maxItems: 100
          items:
            type: string
        integration_run_ids:
          type: array
          maxItems: 100
          items:
            type: string
        source_kinds:
          type: array
          maxItems: 3
          items:
            type: string
            enum:
              - recurring
              - batch
              - file
        boundary_at_from:
          type: string
          format: date-time
        boundary_at_to:
          type: string
          format: date-time
    IntegrationMaterializationPreview:
      type: object
      required:
        - source_observations
        - unique_identities
        - duplicate_observations
        - expired_identities
        - suppressed_identities
        - already_present
        - net_additions
      properties:
        source_observations:
          type: integer
          minimum: 0
        unique_identities:
          type: integer
          minimum: 0
        duplicate_observations:
          type: integer
          minimum: 0
        expired_identities:
          type: integer
          minimum: 0
        suppressed_identities:
          type: integer
          minimum: 0
        already_present:
          type: integer
          minimum: 0
        net_additions:
          type: integer
          minimum: 0
    IntegrationMaterializationPreviewRequest:
      type: object
      required: []
      additionalProperties: false
      properties:
        filters:
          $ref: "#/components/schemas/IntegrationCollectionFilter"
        target_segment_id:
          type: string
    IntegrationMaterializationPreviewResponse:
      type: object
      required:
        - cutoff_at
        - preview
      properties:
        cutoff_at:
          type: string
          format: date-time
        preview:
          $ref: "#/components/schemas/IntegrationMaterializationPreview"
    IntegrationMaterializationRequest:
      type: object
      required:
        - mode
      additionalProperties: false
      properties:
        mode:
          type: string
          enum:
            - new_segment
            - add_to_segment
        segment_name:
          type: string
          minLength: 1
          maxLength: 120
        target_segment_id:
          type: string
        filters:
          $ref: "#/components/schemas/IntegrationCollectionFilter"
    IntegrationMaterialization:
      type: object
      required:
        - materialization_id
        - collection_id
        - mode
        - status
        - filters
        - source_cutoff_at
        - target_segment_id
        - target_segment_name
        - target_segment_version
        - preview
        - identity_count
        - match_levels
        - failure_code
        - failure_message
        - created_at
        - updated_at
        - completed_at
      properties:
        materialization_id:
          type: string
        collection_id:
          type: string
        mode:
          type: string
          enum:
            - new_segment
            - add_to_segment
        status:
          type: string
          enum:
            - created
            - processing
            - completed
            - failed
        filters:
          $ref: "#/components/schemas/IntegrationCollectionFilter"
        source_cutoff_at:
          type: string
          format: date-time
        target_segment_id:
          type: string
        target_segment_name:
          type:
            - string
            - "null"
        target_segment_version:
          type:
            - integer
            - "null"
          minimum: 1
        preview:
          oneOf:
            - $ref: "#/components/schemas/IntegrationMaterializationPreview"
            - type: "null"
        identity_count:
          type:
            - integer
            - "null"
          minimum: 0
        match_levels:
          type: array
          items:
            type: string
            enum:
              - I
              - H
              - D
              - S
              - A
        failure_code:
          type:
            - string
            - "null"
        failure_message:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time
    BillingReadinessProjection:
      type: object
      required:
        - requires_card
        - has_default_card
        - collection_method_ok
        - payment_collection_ready
        - status
      properties:
        requires_card:
          type: boolean
        has_default_card:
          type: boolean
        collection_method_ok:
          type: boolean
        payment_collection_ready:
          type: boolean
        status:
          type: string
          enum:
            - ok
            - card_missing
            - collection_method_mismatch
            - not_applicable
    BillingUsageRateProjection:
      type: object
      required:
        - match
        - audience_delivery
        - destination
        - enrichment_field
      properties:
        match:
          type: object
          required:
            - microbatch
            - file
          properties:
            microbatch:
              type: integer
              minimum: 0
            file:
              type: integer
              minimum: 0
        audience_delivery:
          type: object
          required:
            - filter
            - matched
            - similarity
            - propensity
          properties:
            filter:
              type: integer
              minimum: 0
            matched:
              type: integer
              minimum: 0
            similarity:
              type: integer
              minimum: 0
            propensity:
              type: integer
              minimum: 0
        destination:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
        enrichment_field:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
    BillingContractProjection:
      type: object
      required:
        - tier
        - cadence
        - commitment_term_months
        - commitment_amount_cents
        - usage_discount_bps
        - contract_start_at
        - contract_end_at
        - current_period_start_at
        - current_period_end_at
        - pending_change_at
        - scheduled_change
        - usage_rates
      properties:
        tier:
          type:
            - string
            - "null"
          enum:
            - paygo
            - starter
            - growth
            - enterprise
            - null
        cadence:
          type:
            - string
            - "null"
          enum:
            - monthly
            - annual
            - null
        commitment_term_months:
          type: integer
          enum:
            - 0
            - 12
            - 24
            - 36
        commitment_amount_cents:
          type: integer
          minimum: 0
        usage_discount_bps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Contract discount applied to usage rates only; commitment charges
            and minimums are unchanged.
        contract_start_at:
          type:
            - string
            - "null"
          format: date-time
        contract_end_at:
          type:
            - string
            - "null"
          format: date-time
        current_period_start_at:
          type:
            - string
            - "null"
          format: date-time
        current_period_end_at:
          type:
            - string
            - "null"
          format: date-time
        pending_change_at:
          type:
            - string
            - "null"
          format: date-time
        scheduled_change:
          oneOf:
            - $ref: "#/components/schemas/BillingScheduledContractChange"
            - type: "null"
        usage_rates:
          oneOf:
            - $ref: "#/components/schemas/BillingUsageRateProjection"
            - type: "null"
    BillingScheduledContractChange:
      type: object
      required:
        - kind
        - request_id
        - status
        - effective_at
        - previous_contract_end_at
        - successor_contract_id
        - terms
        - error_code
        - created_at
        - updated_at
      properties:
        kind:
          type: string
          enum:
            - replacement
            - renewal
            - cancellation
        request_id:
          type: string
        status:
          type: string
          enum:
            - provisioning
            - scheduled
            - failed
        effective_at:
          type: string
          format: date-time
        previous_contract_end_at:
          type:
            - string
            - "null"
          format: date-time
        successor_contract_id:
          type:
            - string
            - "null"
        terms:
          oneOf:
            - type: object
              additionalProperties: false
              required:
                - tier
                - cadence
                - commitment_term_months
                - commitment_amount_cents
                - usage_discount_bps
                - usage_rates
              properties:
                tier:
                  type: string
                  enum:
                    - paygo
                    - starter
                    - growth
                    - enterprise
                cadence:
                  type: string
                  enum:
                    - monthly
                    - annual
                commitment_term_months:
                  type: integer
                  enum:
                    - 0
                    - 12
                    - 24
                    - 36
                commitment_amount_cents:
                  type: integer
                  minimum: 0
                usage_discount_bps:
                  type: integer
                  minimum: 0
                  maximum: 10000
                usage_rates:
                  $ref: "#/components/schemas/BillingUsageRateProjection"
            - type: "null"
        error_code:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    BillingMoneyProjection:
      type: object
      required:
        - available_balance_cents
        - committed_balance_cents
        - promotional_balance_cents
        - current_period_spend_cents
        - current_invoice_total_cents
        - next_credit_expiry_at
        - active_reserved_cents
        - accepted_pending_cents
        - effective_available_cents
      properties:
        available_balance_cents:
          type:
            - integer
            - "null"
        committed_balance_cents:
          type:
            - integer
            - "null"
        promotional_balance_cents:
          type:
            - integer
            - "null"
        current_period_spend_cents:
          type: integer
          minimum: 0
        current_invoice_total_cents:
          type:
            - integer
            - "null"
        next_credit_expiry_at:
          type:
            - string
            - "null"
          format: date-time
        active_reserved_cents:
          type: integer
          minimum: 0
        accepted_pending_cents:
          type: integer
          minimum: 0
        effective_available_cents:
          type:
            - integer
            - "null"
    BillingSnapshot:
      type: object
      required:
        - billing_account_id
        - owner_type
        - owner_id
        - source
        - consumer_org_id
        - billing_authority
        - agency_id
        - consumer_postpay_ceiling_cents
        - consumer_money
        - billing_model
        - collection_method
        - currency
        - status
        - contract
        - money
        - payment_method_ready
        - auto_recharge
        - latest_invoice
        - collection
        - projection
      properties:
        billing_account_id:
          type: string
        owner_type:
          type: string
          enum:
            - org
            - agency
        owner_id:
          type: string
        source:
          type: string
        consumer_org_id:
          type: string
        billing_authority:
          type: string
          enum:
            - self
            - agency
        agency_id:
          type:
            - string
            - "null"
        consumer_postpay_ceiling_cents:
          type:
            - integer
            - "null"
        consumer_money:
          type: object
          required:
            - current_period_spend_cents
            - active_reserved_cents
            - accepted_pending_cents
            - projected_at
          properties:
            current_period_spend_cents:
              type: integer
              minimum: 0
            active_reserved_cents:
              type: integer
              minimum: 0
            accepted_pending_cents:
              type: integer
              minimum: 0
            projected_at:
              type:
                - string
                - "null"
              format: date-time
        billing_model:
          type: string
          enum:
            - prepay
            - postpay
        collection_method:
          type: string
          enum:
            - charge_automatically
            - send_invoice
        currency:
          type: string
          const: usd
        status:
          type: string
          enum:
            - pending
            - active
            - payment_action_required
            - past_due
            - suspended
            - canceled
            - provisioning_failed
        contract:
          $ref: "#/components/schemas/BillingContractProjection"
        money:
          $ref: "#/components/schemas/BillingMoneyProjection"
        payment_method_ready:
          type: boolean
        auto_recharge:
          $ref: "#/components/schemas/BillingAutoRechargeProjection"
        latest_invoice:
          type: object
          required:
            - id
            - status
          properties:
            id:
              type:
                - string
                - "null"
            status:
              type:
                - string
                - "null"
        collection:
          $ref: "#/components/schemas/BillingCollectionProjection"
        projection:
          type: object
          required:
            - projected_at
            - provider_version
          properties:
            projected_at:
              type:
                - string
                - "null"
              format: date-time
            provider_version:
              type:
                - string
                - "null"
    BillingCollectionProjection:
      type: object
      required:
        - status
        - stripe_invoice_id
        - failure_started_at
        - grace_period_ends_at
        - updated_at
      properties:
        status:
          type: string
          enum:
            - current
            - past_due
            - uncollectible
        stripe_invoice_id:
          type:
            - string
            - "null"
        failure_started_at:
          type:
            - string
            - "null"
          format: date-time
        grace_period_ends_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
    BillingAutoRechargeProjection:
      type: object
      required:
        - configured
        - enabled
        - threshold_cents
        - recharge_to_cents
        - configuration_request_id
        - payment_status
        - last_payment_attempt_id
        - stripe_invoice_id
        - stripe_payment_intent_id
        - last_error_code
        - updated_at
        - pending_change
      properties:
        configured:
          type: boolean
        enabled:
          type: boolean
        threshold_cents:
          type:
            - integer
            - "null"
          minimum: 500
        recharge_to_cents:
          type:
            - integer
            - "null"
          minimum: 1500
          maximum: 5000000
        configuration_request_id:
          type:
            - string
            - "null"
        payment_status:
          type: string
          enum:
            - none
            - pending
            - action_required
            - paid
            - failed
        last_payment_attempt_id:
          type:
            - string
            - "null"
        stripe_invoice_id:
          type:
            - string
            - "null"
        stripe_payment_intent_id:
          type:
            - string
            - "null"
        last_error_code:
          type:
            - string
            - "null"
        updated_at:
          type:
            - string
            - "null"
          format: date-time
        pending_change:
          oneOf:
            - type: object
              additionalProperties: true
              required:
                - request_id
                - enabled
                - threshold_cents
                - recharge_to_cents
                - status
                - last_error_code
                - created_at
                - updated_at
              properties:
                request_id:
                  type: string
                enabled:
                  type: boolean
                threshold_cents:
                  type: integer
                  minimum: 500
                recharge_to_cents:
                  type: integer
                  minimum: 1500
                  maximum: 5000000
                status:
                  type: string
                  enum:
                    - provisioning
                    - failed
                last_error_code:
                  type:
                    - string
                    - "null"
                created_at:
                  type: string
                  format: date-time
                updated_at:
                  type: string
                  format: date-time
            - type: "null"
    AutoRechargeUpdateRequest:
      type: object
      additionalProperties: false
      required:
        - request_id
        - enabled
        - threshold_cents
        - recharge_to_cents
      properties:
        request_id:
          type: string
          minLength: 8
          maxLength: 160
        enabled:
          type: boolean
        threshold_cents:
          type: integer
          minimum: 500
          maximum: 4999000
        recharge_to_cents:
          type: integer
          minimum: 1500
          maximum: 5000000
    AccountAutoRechargeResponse:
      type: object
      required:
        - billing_account_id
        - auto_recharge
        - action_url
      properties:
        billing_account_id:
          type: string
        auto_recharge:
          $ref: "#/components/schemas/BillingAutoRechargeProjection"
        action_url:
          type:
            - string
            - "null"
          format: uri
    AccountBillingResponse:
      type: object
      required:
        - billing
        - billing_mode
        - parent_org
        - billing_readiness
        - operational
        - projection
      properties:
        billing:
          $ref: "#/components/schemas/BillingSnapshot"
        billing_mode:
          type: string
          enum:
            - org_billed
            - parent_billed
        parent_org:
          oneOf:
            - type: object
              required:
                - id
                - name
              properties:
                id:
                  type:
                    - string
                    - "null"
                name:
                  type:
                    - string
                    - "null"
            - type: "null"
        billing_readiness:
          $ref: "#/components/schemas/BillingReadinessProjection"
        operational:
          type: object
          required:
            - frozen
            - compute_usage_pct
          properties:
            frozen:
              type: boolean
            compute_usage_pct:
              type: number
              minimum: 0
        projection:
          type: object
          required:
            - projected_at
            - provider_version
            - stale
            - refresh_error_code
          properties:
            projected_at:
              type:
                - string
                - "null"
              format: date-time
            provider_version:
              type:
                - string
                - "null"
            stale:
              type: boolean
            refresh_error_code:
              type:
                - string
                - "null"
    BillingCreditBalance:
      type: object
      required:
        - id
        - kind
        - name
        - product_id
        - product_name
        - remaining_cents
        - applicable_product_ids
        - applicable_product_tags
        - source
        - access_schedule
        - next_expiry_at
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - paid_commit
            - promotional_credit
        name:
          type: string
        product_id:
          type: string
        product_name:
          type: string
        remaining_cents:
          type: integer
          minimum: 0
        applicable_product_ids:
          type: array
          items:
            type: string
        applicable_product_tags:
          type: array
          items:
            type: string
        source:
          type:
            - string
            - "null"
        access_schedule:
          type: array
          items:
            type: object
            required:
              - amount_cents
              - starting_at
              - ending_before
              - active
            properties:
              amount_cents:
                type: integer
                minimum: 0
              starting_at:
                type: string
                format: date-time
              ending_before:
                type: string
                format: date-time
              active:
                type: boolean
        next_expiry_at:
          type:
            - string
            - "null"
          format: date-time
    AccountCreditsResponse:
      type: object
      required:
        - billing_account_id
        - balances
      properties:
        billing_account_id:
          type: string
        balances:
          type: array
          items:
            $ref: "#/components/schemas/BillingCreditBalance"
    BillingInvoice:
      type: object
      required:
        - id
        - status
        - type
        - total_cents
        - currency
        - period_start_at
        - period_end_at
        - issued_at
        - line_items
        - collection
      properties:
        id:
          type: string
        status:
          type: string
        type:
          type: string
        total_cents:
          type: integer
        currency:
          type: string
          const: usd
        period_start_at:
          type:
            - string
            - "null"
          format: date-time
        period_end_at:
          type:
            - string
            - "null"
          format: date-time
        issued_at:
          type:
            - string
            - "null"
          format: date-time
        line_items:
          type: array
          items:
            type: object
            required:
              - name
              - type
              - total_cents
              - quantity
              - product_id
              - pricing_dimensions
            properties:
              name:
                type: string
              type:
                type: string
              total_cents:
                type: integer
              quantity:
                type:
                  - number
                  - "null"
              product_id:
                type:
                  - string
                  - "null"
              pricing_dimensions:
                type: object
                additionalProperties:
                  type: string
        collection:
          oneOf:
            - type: object
              required:
                - provider
                - invoice_id
                - status
                - hosted_invoice_url
                - invoice_pdf
                - delivery_error
              properties:
                provider:
                  type: string
                  const: stripe
                invoice_id:
                  type: string
                status:
                  type:
                    - string
                    - "null"
                hosted_invoice_url:
                  type:
                    - string
                    - "null"
                  format: uri
                invoice_pdf:
                  type:
                    - string
                    - "null"
                  format: uri
                delivery_error:
                  type:
                    - string
                    - "null"
            - type: "null"
    AccountInvoicesResponse:
      type: object
      required:
        - billing_account_id
        - authority
        - invoices
      properties:
        billing_account_id:
          type: string
        authority:
          type: string
          enum:
            - commercial
            - mixed
        invoices:
          type: array
          items:
            $ref: "#/components/schemas/BillingInvoice"
    MatchRun:
      type: object
      description: A single micro-batch match run record. `amount_charged` is a
        conservative pending-usage estimate, not a finalized invoice amount.
      required:
        - run_id
        - type
        - status
        - input_record_count
        - match_count
        - match_rate
        - amount_charged
        - field_list
        - created_at
        - expires_at
      properties:
        run_id:
          type: string
        type:
          type: string
          enum:
            - microbatch
        status:
          type: string
          enum:
            - completed
            - failed
        input_record_count:
          type: integer
        match_count:
          type: integer
        match_rate:
          type: number
          format: float
        amount_charged:
          type: number
          format: float
          description: Conservative USD estimate accepted for billing; not a finalized
            invoice total.
        field_list:
          type: array
          items:
            type: string
        created_at:
          type:
            - string
            - "null"
          format: date-time
        expires_at:
          type:
            - string
            - "null"
    MatchRunList:
      type: object
      required:
        - runs
        - has_more
      properties:
        runs:
          type: array
          items:
            $ref: "#/components/schemas/MatchRun"
        has_more:
          type: boolean
    FileMatchJob:
      type: object
      description: A file-based match job (matched-subtype segment)
      required:
        - match_id
        - name
        - status
        - matching_status
        - input_record_count
        - match_count
        - match_rate
        - amount_charged
        - file_format
        - compression
        - segment_id
        - audience_id
        - audience_status
        - source_job_id
        - created_at
        - expires_at
      properties:
        match_id:
          type: string
        name:
          type: string
        status:
          type: string
          enum:
            - pending
            - active
            - failed
            - archived
            - expired
        matching_status:
          type:
            - string
            - "null"
          enum:
            - pending
            - processing
            - completed
            - failed
            - null
        input_record_count:
          type:
            - integer
            - "null"
        match_count:
          type:
            - integer
            - "null"
        match_rate:
          type:
            - number
            - "null"
          format: float
        amount_charged:
          type:
            - number
            - "null"
          description: >
            One-time file-match USD estimate accepted when this run first
            participates in a successful delivery. Null before first delivery;
            excludes the delivery's separate audience/destination/field usage
            and is not a finalized invoice amount.
        file_format:
          type:
            - string
            - "null"
          enum:
            - csv
            - avro
            - json
            - jsonl
            - null
        compression:
          type: string
          enum:
            - none
            - gzip
          description: Input artifact compression, normalized to none for legacy jobs.
        column_mappings:
          type:
            - object
            - "null"
          additionalProperties: true
        error_message:
          type:
            - string
            - "null"
        segment_id:
          type: string
        audience_id:
          type:
            - string
            - "null"
        audience_status:
          type:
            - string
            - "null"
          enum:
            - active
            - archived
            - null
          description: >
            Whether the linked audience (if any) is still active in Library.
            `archived` covers both an explicitly archived audience and a deleted
            one. `null` when there's no `audience_id`, or its audience document
            no longer exists. This job's own history is unaffected either way —
            it persists even after the underlying segment or audience is deleted
            from Library.
        source_job_id:
          type:
            - string
            - "null"
        integration_run_id:
          type: string
          pattern: ^ir_[A-Za-z0-9_-]{40}$
          description: Present only for an authorized private partnership workflow.
        created_at:
          type:
            - string
            - "null"
          format: date-time
        expires_at:
          type:
            - string
            - "null"
    FileMatchJobList:
      type: object
      required:
        - jobs
        - has_more
      properties:
        jobs:
          type: array
          items:
            $ref: "#/components/schemas/FileMatchJob"
        has_more:
          type: boolean
    ErrorResponse:
      type: object
      required:
        - error
      additionalProperties: true
      properties:
        error:
          type: string
          description: >
            Stable machine-readable error code (e.g.
            `INVALID_STATUS_TRANSITION`, `BILLING_INSUFFICIENT_BALANCE`). Always
            present.
        message:
          type: string
          description: Human-readable explanation of the error.
        code:
          type: string
          description: >
            Alternate machine-readable code — present on some endpoints as an
            alias for `error` for backward compatibility.
        request_id:
          type: string
          description: Opaque support/debug identifier when available.
    OAuthErrorResponse:
      type: object
      description: RFC 6749 §5.2 / §4.1.2.1 error body — see the OAuthError response
        component.
      required:
        - error
        - error_description
      properties:
        error:
          type: string
          enum:
            - invalid_request
            - invalid_client
            - invalid_client_metadata
            - invalid_grant
            - invalid_scope
            - invalid_target
            - unsupported_response_type
            - unsupported_grant_type
            - access_denied
            - server_error
        error_description:
          type: string
    OAuthTokenRequest:
      description: >
        Encoded as `application/x-www-form-urlencoded` or `application/json`.
        `client_id`/`client_secret` may instead ride on HTTP Basic auth, in
        which case both fields are omitted from the body.
      oneOf:
        - type: object
          required:
            - grant_type
            - code
            - redirect_uri
            - code_verifier
          properties:
            grant_type:
              type: string
              const: authorization_code
            code:
              type: string
            redirect_uri:
              type: string
              format: uri
            code_verifier:
              type: string
              description: RFC 7636 PKCE verifier — must hash (S256) to the code_challenge you
                sent to /authorize.
            client_id:
              type: string
            client_secret:
              type: string
            resource:
              type: string
              format: uri
        - type: object
          required:
            - grant_type
            - refresh_token
          properties:
            grant_type:
              type: string
              const: refresh_token
            refresh_token:
              type: string
            client_id:
              type: string
            client_secret:
              type: string
            resource:
              type: string
              format: uri
    OAuthTokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
        - refresh_token
        - scope
      properties:
        access_token:
          type: string
          description: "Opaque bearer token. Use exactly like an API-key-derived one:
            `Authorization: Bearer <access_token>`."
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          description: Seconds until the access token expires (1800).
        refresh_token:
          type: string
          description: Supersedes any previously-issued refresh token for this connection
            — the old one is now rejected.
        scope:
          type: string
          description: Space-delimited granted scopes.
    OAuthRevokeRequest:
      type: object
      required:
        - token
      properties:
        token:
          type: string
        token_type_hint:
          type: string
          enum:
            - access_token
            - refresh_token
        client_id:
          type: string
        client_secret:
          type: string
    OAuthServerMetadata:
      type: object
      required:
        - issuer
        - authorization_endpoint
        - token_endpoint
        - revocation_endpoint
        - registration_endpoint
        - response_types_supported
        - grant_types_supported
        - code_challenge_methods_supported
        - token_endpoint_auth_methods_supported
        - client_id_metadata_document_supported
        - resource_indicators_supported
        - scopes_supported
      properties:
        issuer:
          type: string
          format: uri
        authorization_endpoint:
          type: string
          format: uri
        token_endpoint:
          type: string
          format: uri
        revocation_endpoint:
          type: string
          format: uri
        registration_endpoint:
          type: string
          format: uri
        response_types_supported:
          type: array
          items:
            type: string
        grant_types_supported:
          type: array
          items:
            type: string
        code_challenge_methods_supported:
          type: array
          items:
            type: string
        token_endpoint_auth_methods_supported:
          type: array
          items:
            type: string
        client_id_metadata_document_supported:
          type: boolean
        resource_indicators_supported:
          type: boolean
        scopes_supported:
          type: array
          items:
            type: string
    OAuthClientRegistrationRequest:
      type: object
      required:
        - redirect_uris
      properties:
        client_name:
          type: string
        redirect_uris:
          type: array
          minItems: 1
          maxItems: 20
          items:
            type: string
            format: uri
        scope:
          type: string
        token_endpoint_auth_method:
          type: string
          enum:
            - none
        grant_types:
          type: array
          items:
            type: string
            enum:
              - authorization_code
              - refresh_token
        response_types:
          type: array
          items:
            type: string
            enum:
              - code
    OAuthClientRegistrationResponse:
      type: object
      required:
        - client_id
        - client_id_issued_at
        - client_name
        - redirect_uris
        - scope
        - token_endpoint_auth_method
        - grant_types
        - response_types
      properties:
        client_id:
          type: string
        client_id_issued_at:
          type: integer
        client_name:
          type: string
        redirect_uris:
          type: array
          items:
            type: string
            format: uri
        scope:
          type: string
        token_endpoint_auth_method:
          type: string
          enum:
            - none
        grant_types:
          type: array
          items:
            type: string
        response_types:
          type: array
          items:
            type: string
    OAuthProtectedResourceMetadata:
      type: object
      required:
        - resource
        - authorization_servers
        - bearer_methods_supported
        - scopes_supported
        - resource_documentation
      properties:
        resource:
          type: string
          format: uri
        authorization_servers:
          type: array
          items:
            type: string
            format: uri
        bearer_methods_supported:
          type: array
          items:
            type: string
            enum:
              - header
        scopes_supported:
          type: array
          items:
            type: string
        resource_documentation:
          type: string
          format: uri
    NotifiableEventType:
      type: string
      description: >
        Events that can trigger a webhook delivery. Segment and delivery events
        support ordinary durable subscriptions and the org's legacy webhook.
        Partnership events are restricted to the owning OAuth grant and are
        never sent to an org-wide webhook.
      enum:
        - segment.ready
        - segment.failed
        - delivery.completed
        - delivery.failed
        - integration.enrichment.prepared
        - integration.enrichment.failed
        - integration.file.ready
        - integration.file.failed
    WebhookSubscription:
      type: object
      description: >
        A durable event-trigger subscription (e.g. a Zap that is turned on).
        Delivery is retried with exponential backoff; see the Triggers tag for
        the ownership model.
      required:
        - id
        - target_url
        - events
        - status
        - created_at
        - created_by
        - last_delivery_at
        - consecutive_failure_count
      properties:
        id:
          type: string
        target_url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: "#/components/schemas/NotifiableEventType"
        status:
          type: string
          enum:
            - active
            - disabled
            - revoked
          description: >
            "disabled" is set automatically after 20 consecutive dead-lettered
            deliveries; "revoked" is explicit — owner action, disconnecting the
            partnership, or the target endpoint itself returning 410 Gone.
        created_at:
          type: string
          format: date-time
        created_by:
          type: string
          description: Identifier of the creating caller.
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        consecutive_failure_count:
          type: integer
          description: Resets to 0 on any successful delivery; reaching 20 auto-disables
            the subscription.
    WebhookSubscriptionCreated:
      type: object
      description: >
        Response to POST /v1/webhook-subscriptions. `secret` is returned exactly
        once, in plaintext — it is never retrievable again after this response.
      required:
        - id
        - target_url
        - events
        - secret
        - status
        - created_at
      properties:
        id:
          type: string
        target_url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: "#/components/schemas/NotifiableEventType"
        secret:
          type: string
          description: >
            Plaintext HMAC-SHA256 signing secret. Store it now — every
            subsequent read only returns it masked. Used to compute
            `X-IA-Signature` on every delivery to this subscription — see the
            Webhooks tag's Signing section for the exact scheme.
        status:
          type: string
          enum:
            - active
        created_at:
          type: string
          format: date-time
    IntegrationAssetProvenance:
      type: object
      additionalProperties: false
      required:
        - source_provider
        - source_connection_id
        - source_connection_display_name
        - source_run_id
        - source_boundary_at
        - promoted_at
      properties:
        source_provider:
          type: string
        source_connection_id:
          type: string
        source_connection_display_name:
          type:
            - string
            - "null"
        source_run_id:
          type: string
          pattern: ^ir_[A-Za-z0-9_-]{40}$
        source_boundary_at:
          type: string
          format: date-time
        promoted_at:
          type: string
          format: date-time
    IntegrationCollectionProvenance:
      type: object
      additionalProperties: false
      required:
        - version
        - materialization_id
        - collection_id
        - mode
        - providers
        - connection_count
        - source_cutoff_at
        - source_observations
        - unique_identities
        - materialized_at
      properties:
        version:
          type: integer
          const: 1
        materialization_id:
          type: string
          pattern: ^imm_[a-f0-9]{40}$
        collection_id:
          type: string
        mode:
          type: string
          enum:
            - new_segment
            - add_to_segment
        providers:
          type: array
          items:
            type: string
        connection_count:
          type: integer
          minimum: 0
        source_cutoff_at:
          type: string
          format: date-time
        source_observations:
          type: integer
          minimum: 0
        unique_identities:
          type: integer
          minimum: 0
        materialized_at:
          type: string
          format: date-time
    ExpandedSegment:
      type: object
      description: >
        Lightweight summary of a constituent segment, expanded server-side from
        the audience's composition. Returned on both list and single-GET
        audience responses. `subtype` is the segment's own subtype — use the
        first entry of `segments` (the primary included segment) to derive
        subtype-specific presentation for the audience.
      required:
        - id
        - name
        - record_count
        - subtype
        - input_record_count
        - match_count
      properties:
        id:
          type: string
          description: Segment document ID.
        name:
          type: string
          description: Segment display name.
        record_count:
          type:
            - integer
            - "null"
          description: Cached record count of the segment. Null if never counted.
        subtype:
          type: string
          enum:
            - filter
            - matched
            - similarity
            - propensity
          description: Subtype of the constituent segment.
        input_record_count:
          type:
            - integer
            - "null"
          description: matched subtype only. Count of records in the uploaded identity
            file. Null otherwise.
        match_count:
          type:
            - integer
            - "null"
          description: matched subtype only. Count of records successfully matched. Null
            otherwise.
    AudienceObject:
      type: object
      required:
        - audience_id
        - name
        - status
        - version
        - expires_at
        - visibility
        - segment_refs
        - segments
        - excluded_segments
        - integration_provenance
        - created_at
        - updated_at
      properties:
        integration_provenance:
          oneOf:
            - $ref: "#/components/schemas/IntegrationAssetProvenance"
            - type: "null"
          description: Source partnership and boundary for a result saved from Connect;
            null otherwise.
        audience_id:
          type: string
        name:
          type: string
        status:
          type: string
          enum:
            - active
            - archived
            - expired
          description: >
            active = ready for delivery; archived = soft-deleted via PATCH
            status:archived (excluded from list by default); expired = past
            90-day TTL. Note: pending/failed statuses belong on segments, not
            audiences.
        version:
          type: integer
        quote_id:
          type:
            - string
            - "null"
          description: ID of the most recent delivery quote for this audience.
        quote_total_cents:
          type:
            - integer
            - "null"
          description: Total cost in cents from the most recent delivery quote.
        parent_audience_id:
          type:
            - string
            - "null"
          description: ID of the audience this was forked from (null if not a fork).
        forked_from_snapshot_version:
          type:
            - integer
            - "null"
          description: Snapshot version of the parent audience at the time of forking.
        linked_campaign_id:
          type:
            - string
            - "null"
          description: ID of the campaign this audience is linked to, if any.
        record_count:
          type:
            - integer
            - "null"
          description: Total resolved records across all composed segments.
        matched_record_count:
          type:
            - integer
            - "null"
          description: >
            Records contributed by matched-subtype segments in this audience's
            composition. Present only when the composition includes at least one
            included matched segment; null for pure-filter or non-matched
            compositions.
        match_count:
          type:
            - integer
            - "null"
          description: >
            Records resolved to a known identity, for an audience created via
            the direct file-upload flow (POST /v1/match/file). Populated when
            that upload's matching pipeline completes; null before then or for
            audiences not created that way — use matched_record_count for a
            composition-level matched count instead.
        input_record_count:
          type:
            - integer
            - "null"
          description: >
            Rows in the originally uploaded identity file, for an audience
            created via POST /v1/match/file. Same population conditions as
            match_count.
        match_rate:
          type:
            - number
            - "null"
          description: >
            match_count / input_record_count, rounded to 4 decimal places. Null
            unless both match_count and input_record_count are present.
        expires_at:
          type: string
          description: ISO date — 90-day hard expiry from creation
        version_created_at:
          type:
            - string
            - "null"
        visibility:
          type: string
          enum:
            - org
            - private
          description: >
            org (default) — visible to all members of the org. private — visible
            only to the creating user.
        segment_refs:
          type: array
          description: >
            Composition of segments that define this audience. Each entry
            specifies a segment and its role (include or exclude) in the set
            operation.
          items:
            type: object
            required:
              - segment_id
              - role
            properties:
              segment_id:
                type: string
                description: ID of the referenced segment.
              role:
                type: string
                enum:
                  - include
                  - exclude
                description: Whether the segment's records are included in or excluded from the
                  audience.
        segment_ids:
          type: array
          items:
            type: string
          description: Cache of include-role segment IDs (denormalized from segment_refs
            for query efficiency).
        excluded_segment_ids:
          type: array
          items:
            type: string
          description: Cache of exclude-role segment IDs (denormalized from segment_refs
            for query efficiency).
        segments:
          type: array
          description: >
            Expanded summaries of the include-role segments, in `segment_ids`
            order. Populated on both list and single-GET responses. Best-effort
            — missing or deleted segment docs are skipped, so this array may be
            shorter than `segment_ids`.
          items:
            $ref: "#/components/schemas/ExpandedSegment"
        excluded_segments:
          type: array
          description: >
            Expanded summaries of the exclude-role segments, in
            `excluded_segment_ids` order. Same expansion semantics as
            `segments`.
          items:
            $ref: "#/components/schemas/ExpandedSegment"
        set_logic:
          type: string
          enum:
            - union
            - intersection
          description: >
            How the include-role segments are combined: `union` = a record
            matches ANY included segment (OR); `intersection` = a record must
            match EVERY included segment (AND). Segments in
            `excluded_segment_ids` are always subtracted from that result (AND
            NOT), regardless of `set_logic`.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DeliveryObject:
      type: object
      required:
        - delivery_id
        - audience_id
        - audience_version
        - destination
        - status
        - cost
        - created_at
        - expires_at
      properties:
        delivery_id:
          type: string
        audience_id:
          type: string
        audience_version:
          type: integer
        destination:
          type: string
          enum:
            - download
            - liveramp
            - narrative
        template_id:
          type:
            - string
            - "null"
        field_list:
          type:
            - array
            - "null"
          items:
            type: string
        status:
          type: string
          enum:
            - processing
            - completed
            - failed
        cost:
          type: number
          description: Conservative USD estimate for usage accepted for billing; not a
            finalized invoice total.
        estimated_cost:
          type: number
          description: >
            Pre-flight safety ceiling for a still-processing delivery. It is
            released into the final local `cost` estimate once resolution/export
            completes; the finalized invoice remains authoritative.
        already_delivered:
          type: boolean
          description: >
            `true` when this exact audience content, resolved field selection,
            unmatched-row policy, and destination were already delivered —
            `cost` is 0 and the existing delivery record is returned.
        message:
          type: string
          description: Human-readable note, present on free re-delivery responses.
        created_at:
          type: string
        completed_at:
          type:
            - string
            - "null"
        expires_at:
          type: string
        output_format:
          type: string
          enum:
            - csv
            - avro
            - json
            - jsonl
          description: >
            Output file format. Present on `download` deliveries. Avro exports
            preserve native source column types (including `REPEATED`/array
            columns) rather than flattening everything to strings. Read
            endpoints normalize legacy records; an immediate DSP-create response
            may omit this field.
        output_compression:
          type: string
          enum:
            - none
            - gzip
          description: >
            Explicit outer artifact compression, normalized to none for legacy
            records on read and possibly omitted by an immediate DSP-create
            response. It never inherits input compression. Avro uses native
            DEFLATE and therefore reports none here rather than outer gzip.
        output_bytes:
          type:
            - integer
            - "null"
          minimum: 0
          description: >
            Exact sum of stored object sizes across every output part. Null
            until completion or when no downloadable artifact is produced.
        download_urls:
          type:
            - array
            - "null"
          items:
            type: string
          description: >
            Signed download URLs for every completed `download` artifact part,
            sorted deterministically. Always an array and may contain multiple
            export parts for any format. Signed URLs expire after 24 hours —
            call `GET /v1/audiences/{id}/deliveries/{did}` to regenerate fresh
            URLs at any time.
        output_record_count:
          type:
            - integer
            - "null"
          description: >
            Actual row count in the exported file. Set at completion for matched
            audiences only (where the count is unknown until the TVF runs). For
            filter, similarity, and propensity audiences this equals the
            audience's `record_count`.
        failure_reason:
          type:
            - string
            - "null"
        matched_segment_id:
          type:
            - string
            - "null"
          description: >
            ID of the matched (file-match) segment resolved from the delivered
            audience's composition at delivery creation. Populated only when the
            audience includes a matched segment; null for filter, similarity,
            and propensity deliveries and for deliveries created before this
            attribute existed.
        source_job_id:
          type:
            - string
            - "null"
          description: >
            The matched segment's originating file-match job ID — links the
            delivery back to the file-match event that produced its source data.
            Populated only when the delivered audience resolves a matched
            segment that carries a source job; null otherwise (including
            deliveries created before this attribute existed).
        integration_run_id:
          type: string
          pattern: ^ir_[A-Za-z0-9_-]{40}$
          description: Present only for an authorized private partnership workflow.
    AudienceRefreshResult:
      type: object
      description: >
        Response of `POST /v1/audiences/{id}/refresh`. Deliberately lighter than
        the full Audience object: the handler returns only the version/lifecycle
        attributes it changed plus subtype-specific next-step hints. Fetch `GET
        /v1/audiences/{id}` for the full audience document.
      required:
        - audience_id
        - segment_id
        - version
        - version_created_at
        - expires_at
        - status
        - subtype
        - refreshed
        - next_step
      properties:
        audience_id:
          type: string
        segment_id:
          type: string
          description: >
            The caller-chosen member segment that was refreshed (echoed back
            from the request body). An audience is a composition of N member
            segments with no primary one — this is always present, identifying
            which member segment's refresh drove this response.
        version:
          type: integer
          description: New snapshot version after the refresh.
        version_created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: New 90-day hard expiry.
        status:
          type: string
          enum:
            - active
            - pending
          description: >
            active = the refreshed segment's subtype is filter, refreshed
            synchronously; pending = matched/similarity/propensity, awaiting
            re-upload or workflow completion.
        subtype:
          type: string
          enum:
            - filter
            - matched
            - similarity
            - propensity
          description: Subtype of the refreshed segment.
        refreshed:
          type: boolean
          description: >
            true when the refreshed segment was re-derived; false only for the
            filter-subtype no-op path (segment already active, count already
            null, TTL already fresh) — this route always calls
            refreshSegmentCore without force, so that no-op path is reachable
            here exactly as it is on POST /v1/segments/{id}/refresh.
        next_step:
          type: string
          description: >
            Human-readable hint for the calling agent on how to proceed (e.g.
            recount, upload to upload_url, or wait for the workflow).
        upload_url:
          type: string
          description: >
            Presigned upload URL (24 h expiry). Present only for the matched
            subtype, when shard_count was 1 (the default) — absent when sharded,
            use upload_urls instead.
        upload_urls:
          type: array
          items:
            type: string
          description: >
            Presigned upload URLs (24 h expiry), one per shard, index-ordered.
            Present only for the matched subtype when shard_count > 1 was
            requested — upload_url is absent. Every CSV shard must include the
            same header row.
        compression:
          type: string
          enum:
            - none
            - gzip
          description: Compression for this matched refresh upload.
        upload_expires_at:
          type: string
          format: date-time
          description: Expiry for upload_url/upload_urls. Present only for the matched
            subtype.
        workflow_run_id:
          type: string
          description: >
            Cloud Workflow execution ID. Present only for similarity and
            propensity subtypes.
    SegmentRefreshResult:
      description: >
        Response of `POST /v1/segments/{id}/refresh` — the full updated segment
        document plus refresh metadata. For the matched subtype the response
        additionally carries upload_url / upload_expires_at (24 h presign) — or
        upload_urls (an array, one per shard) when shard_count > 1 was
        requested, in which case upload_url is absent; for similarity/propensity
        the segment's workflow_run_id points at the newly launched run.
      allOf:
        - $ref: "#/components/schemas/SegmentObject"
        - type: object
          required:
            - refreshed
            - affected_audiences
            - next_step
          properties:
            refreshed:
              type: boolean
              description: >
                true when the segment was re-derived; false when the filter
                no-op path returned the current object unchanged (already
                active, count already null, TTL already fresh — pass force: true
                to bump anyway).
            affected_audiences:
              type: array
              items:
                type: string
              description: >
                IDs of non-deleted audiences that reference this segment. They
                are NOT auto-refreshed — their status, record_count, and expiry
                may now be stale. Recount or refresh them as needed, or use POST
                /v1/audiences/{id}/refresh for a whole-composition refresh.
            next_step:
              type: string
              description: >
                Human-readable hint for the calling agent on how to proceed
                (recount, upload to upload_url, or wait for the workflow).
    Filter:
      type: object
      description: A single filter condition for audience discovery or job execution
      required:
        - field
        - op
      properties:
        field:
          type: string
          description: The canonical attribute name (e.g. 'age', 'state')
        op:
          type: string
          enum:
            - =
            - "!="
            - <
            - <=
            - ">"
            - ">="
            - IN
            - NOT IN
            - BETWEEN
            - LIKE
            - ARRAY_CONTAINS
            - ARRAY_CONTAINS_ANY
            - IS NULL
            - IS NOT NULL
          description: Comparison operator
        value:
          oneOf:
            - type: string
            - type: number
            - type: boolean
            - type: array
              items:
                oneOf:
                  - type: string
                  - type: number
          description: >
            The value to match against. Scalar types (`string`, `number`,
            `boolean`) are used for single-value operators (`=`, `!=`, `<`,
            `<=`, `>`, `>=`, `LIKE`, `ARRAY_CONTAINS`). Pass an **array** for
            `IN`, `NOT IN`, and `ARRAY_CONTAINS_ANY` — pass the array directly
            with no comma-encoding. `BETWEEN` takes a two-element array `[low,
            high]`. Omit entirely for `IS NULL` / `IS NOT NULL` checks.
    FilterGroup:
      type: object
      description: >
        A group of Filter conditions. All `filters` within a single group are
        **always ANDed together** — there is no per-group intra-operator. The
        `combinator` attribute is an **inter-group** operator: it controls how
        this group is joined to the immediately preceding group in the array.
        The `combinator` on the first group (index 0) is always ignored.


        Example — `(city = "NYC" AND state = "NY") OR (state = "CA")`:

        ```json [
          { "id": "g1", "filters": [{"field":"city","op":"=","value":"NYC"},
                                    {"field":"state","op":"=","value":"NY"}],
            "combinator": "AND" },
          { "id": "g2", "filters": [{"field":"state","op":"=","value":"CA"}],
            "combinator": "OR" }
        ] ```

        Group g1's `combinator` is irrelevant (it is the first group). Group
        g2's `combinator: "OR"` means the result is `(g1) OR (g2)`.
      required:
        - id
        - filters
        - combinator
      properties:
        id:
          type: string
          description: >
            Stable identifier (UUID or NanoID). Used to track groups across
            edits.
        filters:
          type: array
          items:
            $ref: "#/components/schemas/Filter"
          description: >
            One or more filter conditions. All conditions in this array are
            combined with AND. Provide at least one filter per group.
        combinator:
          type: string
          enum:
            - AND
            - OR
          description: >
            **Inter-group operator.** Joins this group to the previous group in
            the array. `AND` narrows the result; `OR` broadens it. Ignored on
            the first group (index 0) — that group has no predecessor to join.
    DiscoveryInput:
      type: object
      description: >
        One side of a discovery operation — provide exactly one of `filters`,
        `filter_groups`, `segment_id`, `audience_id`, or `segment_ids` +
        `set_logic`. Providing more than one returns 400 `AMBIGUOUS_INPUT`;
        providing none returns 400 `NO_INPUT_SPECIFIED`.
      properties:
        filters:
          type: array
          description: Flat filter list.
          items:
            $ref: "#/components/schemas/Filter"
        filter_groups:
          type: array
          description: Compound boolean filter groups.
          items:
            $ref: "#/components/schemas/FilterGroup"
        segment_id:
          type: string
          description: ID of a saved segment.
        audience_id:
          type: string
          description: ID of a saved audience.
        segment_ids:
          type: array
          items:
            type: string
          description: IDs of multiple segments to combine (requires `set_logic`).
        set_logic:
          type: string
          enum:
            - union
            - intersection
          description: >
            How to combine segment_ids: union = match ANY segment (OR);
            intersection = match EVERY segment (AND).
    DistributionBucket:
      type: object
      description: One bucket of a categorical distribution.
      required:
        - label
        - count
      properties:
        label:
          type: string
        count:
          type: integer
    BulkCreateSegmentsResult:
      type: object
      description: >
        Result of POST /v1/segments/bulk-create. Returned with 201 when at least
        one segment was created, and with 400 (same shape) when every item
        failed.
      required:
        - created
        - failed
        - segments
        - errors
      properties:
        created:
          type: integer
          description: Number of segments successfully created.
        failed:
          type: integer
          description: Number of input items that could not be created.
        segments:
          type: array
          description: Full segment documents for each successfully created segment.
          items:
            allOf:
              - $ref: "#/components/schemas/SegmentObject"
              - type: object
                properties:
                  audience_id:
                    type: string
                    description: >
                      Auto-created audience wrapper ID. Present only when the
                      input item set create_audience: true.
        errors:
          type: array
          description: Per-item failures. Empty when all items succeeded.
          items:
            type: object
            required:
              - index
              - name
              - error
            properties:
              index:
                type: integer
                description: Zero-based position of the failed item in the input `segments`
                  array.
              name:
                type: string
                description: The `name` from the failed input item.
              error:
                type: string
                description: Human-readable reason the segment could not be created.
    SegmentObject:
      type: object
      required:
        - segment_id
        - name
        - subtype
        - status
        - record_count
        - expires_at
        - ephemeral
        - integration_provenance
        - integration_collection_provenance
        - created_at
        - updated_at
      properties:
        integration_provenance:
          oneOf:
            - $ref: "#/components/schemas/IntegrationAssetProvenance"
            - type: "null"
          description: Source partnership and boundary for a result saved from Connect;
            null otherwise.
        integration_collection_provenance:
          oneOf:
            - $ref: "#/components/schemas/IntegrationCollectionProvenance"
            - type: "null"
          description: Bounded source summary for a segment built from saved integration
            matches; null otherwise.
        segment_id:
          type: string
          description: Unique segment identifier
        name:
          type: string
          description: Display name of the segment
        subtype:
          type: string
          enum:
            - filter
            - matched
            - similarity
            - propensity
          description: >
            Segment subtype. Determines which additional attributes are present.
            filter = saved filter criteria; matched = customer list upload with
            identity resolution; similarity = lookalike model; propensity = ML
            scoring model.
        status:
          type: string
          enum:
            - pending
            - active
            - failed
            - archived
            - expired
          description: >
            pending = computation in progress (matched/similarity/propensity
            during creation); active = fully ready; failed = workflow or
            enrichment error (see error_message); archived = hidden from the
            default segment catalog and new audience compositions, but otherwise
            fully functional — audiences that already reference an archived
            segment are unaffected, and it can be restored with PATCH
            status:active at any time. Distinct from DELETE, which is not
            reversible and does change referencing audiences' composition;
            expired = past 90-day TTL.
        record_count:
          type:
            - integer
            - "null"
          description: Total matched/qualified records. Null while pending or if count has
            not been run.
        expires_at:
          type: string
          description: >
            Date-only ISO string (90-day hard expiry from creation) for a
            persistent segment; a full ISO datetime (short, hour-granular TTL)
            while `ephemeral` is true.
        ephemeral:
          type: boolean
          description: >
            True for a scratch/free/auto-expiring filter-subtype segment.
            Absent/false reads as persistent. Promote to persistent via `PATCH
            /v1/segments/{id} {ephemeral: false}`, or implicitly by composing it
            into an audience.
        promoted_at:
          type: string
          description: >
            ISO datetime the segment was promoted from ephemeral to persistent.
            Absent until promotion happens.
        promoted_via:
          type: string
          enum:
            - explicit_save
            - composition
          description: >
            How the segment was promoted to persistent — `explicit_save` (a
            direct `ephemeral: false` PATCH) or `composition` (referenced by an
            audience create/update). Absent until promotion happens.
        visibility:
          type: string
          enum:
            - org
            - private
          description: >
            org (default) — visible to all members of the org. private — visible
            only to the creating user.
        current_version:
          type:
            - integer
            - "null"
          description: filter subtype only. Current version counter.
        filters:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/Filter"
          description: filter subtype only. Legacy flat filter array.
        filter_groups:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/FilterGroup"
          description: filter subtype only. Grouped filter conditions.
        file_format:
          type:
            - string
            - "null"
          enum:
            - csv
            - avro
            - json
            - jsonl
          description: matched subtype only. Input file format.
        compression:
          type:
            - string
            - "null"
          enum:
            - none
            - gzip
            - null
          description: >
            matched subtype only. Input artifact compression, normalized to none
            by current handlers.
        shard_count:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 50
          description: >
            matched subtype only. Number of same-format input objects,
            normalized to 1 for unsharded uploads.
        column_mappings:
          type:
            - object
            - "null"
          additionalProperties:
            type: string
          description: >
            matched subtype only. Source column name → standard identity
            attribute mappings. Populated after the analyze-and-store workflow
            gate runs.
        mapping_confirmed:
          type:
            - boolean
            - "null"
          description: matched subtype only. True once column mappings have been confirmed.
        input_record_count:
          type:
            - integer
            - "null"
          description: matched subtype only. Number of rows in the original upload.
        match_count:
          type:
            - integer
            - "null"
          description: matched subtype only. Rows that resolved to a known identity after
            enrichment.
        match_rate:
          type:
            - number
            - "null"
          description: matched subtype only. match_count / input_record_count.
        upload_url:
          type:
            - string
            - "null"
          description: >
            matched subtype only. 30-minute signed upload URL. Present on create
            and refresh responses only, when shard_count was 1 (the default) —
            absent when sharded, use upload_urls instead.
        upload_urls:
          type:
            - array
            - "null"
          items:
            type: string
          description: >
            matched subtype only. N signed upload URLs (index-ordered). Present
            on create and refresh responses only when shard_count > 1 was
            requested — upload_url is absent. Every CSV shard must include the
            same header row.
        upload_expires_at:
          type:
            - string
            - "null"
          format: date-time
          description: matched subtype only. Expiry of upload_url/upload_urls.
        error_message:
          type:
            - string
            - "null"
          description: |
            Present when status is failed. Describes the reason for failure.
        source_data_segment_id:
          type:
            - string
            - "null"
          description: >
            matched subtype only. Opaque, read-only reference to stored match
            results when they are shared or versioned independently from the
            segment metadata. Null when no separate result reference is needed.
        seed_description:
          type:
            - string
            - "null"
          description: similarity subtype only. Natural language ICP persona description
            used as seed.
        seed_segment_id:
          type:
            - string
            - "null"
          description: similarity subtype only. ID of an existing segment used as the
            lookalike seed.
        generation_metadata_id:
          type:
            - string
            - "null"
          description: similarity/propensity subtype. References the lookalike run or
            propensity model ID.
        workflow_run_id:
          type:
            - string
            - "null"
          description: >
            similarity and propensity subtypes. The workflow run ID. Poll GET
            /v1/workflows/{workflow_run_id} to monitor progress.
        positive_class_segment_id:
          type:
            - string
            - "null"
          description: propensity subtype only. ID of the segment whose members are the
            positive training class.
        parent_segment_id:
          type:
            - string
            - "null"
          description: ID of the segment this was duplicated or derived from, if applicable.
        derived_from:
          type:
            - string
            - "null"
          description: >
            Root ancestor segment ID. Stable across duplicate-of-duplicate
            chains — always points at the original segment, never an
            intermediate duplicate.
        parent_segment_version:
          type:
            - integer
            - "null"
          description: >
            Version of the parent segment pinned at duplicate time
            (current_version for filter parents; snapshot_version otherwise).
            Null on segments that are not duplicates.
        origin_campaign_id:
          type:
            - string
            - "null"
          description: >
            ID of the campaign whose chat session first produced this segment.
            Metadata only — does not restrict the segment to that campaign.
            Present only when the segment was created via POST /v1/segments with
            a campaign_id.
        usage:
          type: object
          description: >
            Cross-campaign usage stats. Only present on GET /v1/segments/{id} —
            not included on list responses. Useful for understanding impact
            before archiving.
          properties:
            audience_count:
              type: integer
              description: Number of active audiences that reference this segment.
            campaign_count:
              type: integer
              description: Number of distinct campaigns those audiences belong to.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DeliveryConfig:
      type: object
      description: >
        Configured delivery destination. The `type` attribute selects the
        channel; partner destinations require additional identifying attributes.
      oneOf:
        - description: >
            Direct file download — exports are returned through signed,
            time-limited download URLs.
          required:
            - type
          properties:
            type:
              type: string
              const: download
        - description: >
            Push to a LiveRamp seat for identity resolution and onboarding.
            `liveramp_seat_id` is required for the routing to work.
          required:
            - type
            - liveramp_seat_id
          properties:
            type:
              type: string
              const: liveramp
            liveramp_seat_id:
              type: string
              description: LiveRamp seat ID that identifies the target onboarding seat.
        - description: >
            Push to a Narrative.io dataset for data marketplace distribution.
            `narrative_dataset_id` is required for the routing to work.
          required:
            - type
            - narrative_dataset_id
          properties:
            type:
              type: string
              const: narrative
            narrative_dataset_id:
              type: string
              description: Narrative.io dataset ID that identifies the target dataset.
    Campaign:
      type: object
      required:
        - id
        - name
        - description
        - status
        - session_ids
        - audiences
        - created_by
        - updated_by
        - created_at
        - updated_at
      description: >
        A named workspace grouping related audiences and deliveries. There is no
        audience_ids[] field — audiences[] (below) is the only audience-linkage
        field the handler ever returns.
      properties:
        id:
          type: string
          description: Unique campaign identifier.
        name:
          type: string
          description: Display name of the campaign.
        description:
          type: string
          maxLength: 1000
          description: User-authored campaign brief, intended market, goals, and
            constraints.
        status:
          type: string
          enum:
            - active
            - archived
          description: Lifecycle state of the campaign.
        session_ids:
          type: array
          items:
            type: string
          description: Associated chat session IDs.
        audiences:
          type: array
          description: >
            Audiences linked to this campaign — expanded on GET
            /v1/campaigns/{id}; empty array on list responses.
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              status:
                type: string
                enum:
                  - active
                  - archived
                  - expired
              record_count:
                type:
                  - integer
                  - "null"
              segment_ids:
                type: array
                items:
                  type: string
                description: IDs of include-role segments in this audience.
              excluded_segment_ids:
                type: array
                items:
                  type: string
                description: IDs of exclude-role segments in this audience.
              segments:
                type: array
                description: >
                  Expanded summaries of the include-role segments, in
                  `segment_ids` order (best-effort — missing or deleted segment
                  docs are skipped). The first entry is the primary segment; its
                  `subtype` determines the audience's subtype-specific behavior.
                items:
                  $ref: "#/components/schemas/ExpandedSegment"
              excluded_segments:
                type: array
                description: Expanded summaries of the exclude-role segments, same semantics as
                  `segments`.
                items:
                  $ref: "#/components/schemas/ExpandedSegment"
              set_logic:
                type: string
                enum:
                  - union
                  - intersection
              snapshot_version:
                type: integer
              parent_audience_id:
                type:
                  - string
                  - "null"
              linked_at:
                type:
                  - string
                  - "null"
                format: date-time
                description: When this audience was linked to the campaign.
              linked_by:
                type:
                  - string
                  - "null"
                description: UID of the user who linked this audience to the campaign.
        created_by:
          type:
            - string
            - "null"
          description: UID of the user who created the campaign.
        updated_by:
          type:
            - string
            - "null"
          description: UID of the user who last updated the campaign.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    OperationEventType:
      type: string
      description: >
        The full set of operational event types the Ops Rail / audit log can
        record — a superset of the webhook event strings (`segment.ready`,
        `segment.failed`, `delivery.completed`, `delivery.failed`), which map
        onto `segment.build.completed`, `segment.build.failed`,
        `delivery.completed`, and `delivery.failed` respectively.
      enum:
        - segment.build.started
        - segment.build.progress
        - segment.build.completed
        - segment.build.failed
        - segment.created
        - segment.promoted
        - segment.refresh.started
        - segment.refresh.completed
        - segment.refresh.failed
        - audience.created
        - audience.updated
        - audience.build.failed
        - delivery.started
        - delivery.completed
        - delivery.failed
        - match.microbatch.completed
        - integration.enrichment.started
        - integration.enrichment.prepared
        - integration.enrichment.completed
        - integration.enrichment.failed
        - integration.activation.started
        - integration.activation.completed
        - integration.activation.failed
        - invoice.paid
        - invoice.collection_failed
        - invoice.collection_recovered
        - billing.auto_recharge_updated
        - billing.provisioning.started
        - billing.provisioning.progressed
        - billing.provisioning.completed
        - billing.provisioning.failed
        - subscription.tier_changed
        - subscription.commitment_renewed
        - subscription.cancellation_scheduled
        - subscription.cancellation_reverted
        - subscription.external_plan_activated
        - subscription.external_plan_suspended
        - org.provisioned
        - agency.provisioned
    OperationEvent:
      type: object
      description: >
        A single, immutable status transition in the org's operational event
        log. Multiple events can share the same `operation_key` — e.g. a
        matched-segment build emits a `segment.build.started` event and, later,
        a separate `segment.build.completed` (or `.failed`) event, each with its
        own `ts`.
      required:
        - event_id
        - org_id
        - type
        - status
        - is_terminal
        - operation_key
        - actor_uid
        - campaign_id
        - subject
        - subtype
        - category
        - payload
        - ts
      properties:
        event_id:
          type: string
        org_id:
          type: string
        type:
          $ref: "#/components/schemas/OperationEventType"
        status:
          type: string
          description: The status at this specific timestamp (e.g. `pending`,
            `processing`, `completed`, `failed`).
        is_terminal:
          type: boolean
          description: >
            False means the underlying operation was still in flight as of this
            event — the Ops Rail renders these with a pulsing marker.
        operation_key:
          type: string
          description: >
            Stable identifier for the underlying operation (e.g.
            `segment:seg_123`, `delivery:del_456`) — groups multiple events into
            one timeline.
        actor_uid:
          type:
            - string
            - "null"
          description: UID of the user who initiated this specific transition.
        campaign_id:
          type:
            - string
            - "null"
        subject:
          type: object
          required:
            - kind
            - id
            - name
          properties:
            kind:
              type: string
              enum:
                - segment
                - audience
                - delivery
                - match
                - invoice
                - subscription
                - integration
                - org
                - agency
            id:
              type: string
            name:
              type:
                - string
                - "null"
        subtype:
          type:
            - string
            - "null"
          enum:
            - filter
            - matched
            - similarity
            - propensity
            - null
        category:
          type: string
          enum:
            - operational
        payload:
          type: object
          additionalProperties: true
          description: >
            Event-type-specific extras (e.g. `progress_pct`, `destination`,
            `failure_reason`, `record_count`) — shape varies by `type`.
        ts:
          type: string
          format: date-time
    NotificationPreferences:
      type: object
      description: >
        Per-event-type email opt-in flags for ordinary segment and delivery
        events. Connection-bound partnership callbacks are automation-only and
        are not personal email preferences. Absent/false = opt-out (default) —
        no email until the user explicitly opts in.
      properties:
        segment.ready:
          type: boolean
        segment.failed:
          type: boolean
        delivery.completed:
          type: boolean
        delivery.failed:
          type: boolean
      additionalProperties: false
    NotificationPreferencesResponse:
      type: object
      required:
        - notification_preferences
      properties:
        notification_preferences:
          type: object
          required:
            - segment.ready
            - segment.failed
            - delivery.completed
            - delivery.failed
          properties:
            segment.ready:
              type: boolean
            segment.failed:
              type: boolean
            delivery.completed:
              type: boolean
            delivery.failed:
              type: boolean
    SegmentWebhookReadyPayload:
      type: object
      description: >
        Canonical envelope posted to your webhook when an async segment (matched
        file upload, similarity, or propensity) completes successfully.
      required:
        - id
        - type
        - created_at
        - api_version
        - org_id
        - subject
        - data
        - links
      properties:
        id:
          type: string
          description: Unique per event firing — stable across retries of the same delivery.
          example: evt_1a2b3c4d5e6f7a8b9c0d1e2f
        type:
          type: string
          enum:
            - segment.ready
        created_at:
          type: string
          format: date-time
        api_version:
          type: string
          example: 2026-08-06
        org_id:
          type: string
        subject:
          type: object
          description: Identifies the segment this event is about.
          required:
            - kind
            - id
            - name
          properties:
            kind:
              type: string
              enum:
                - segment
            id:
              type: string
            name:
              type:
                - string
                - "null"
        data:
          type: object
          required:
            - status
          description: >
            `status` is always present; the remaining fields vary by segment
            subtype and creation path — treat any field not listed here as
            additive.
          properties:
            status:
              type: string
              enum:
                - completed
            audience_id:
              type:
                - string
                - "null"
              description: >
                If create_audience was true when the segment was created, the ID
                of the auto-created audience wrapper. Null otherwise.
            match_count:
              type:
                - integer
                - "null"
              description: Present for matched subtype only.
            record_count:
              type:
                - integer
                - "null"
          additionalProperties: true
        links:
          type: object
          additionalProperties: true
          description: Reserved for future related-resource links. Currently always empty.
    SegmentWebhookFailedPayload:
      type: object
      description: >
        Canonical envelope posted to your webhook when an async segment build
        fails due to an enrichment or workflow error.
      required:
        - id
        - type
        - created_at
        - api_version
        - org_id
        - subject
        - data
        - links
      properties:
        id:
          type: string
          example: evt_1a2b3c4d5e6f7a8b9c0d1e2f
        type:
          type: string
          enum:
            - segment.failed
        created_at:
          type: string
          format: date-time
        api_version:
          type: string
          example: 2026-08-06
        org_id:
          type: string
        subject:
          type: object
          description: Identifies the segment this event is about.
          required:
            - kind
            - id
            - name
          properties:
            kind:
              type: string
              enum:
                - segment
            id:
              type: string
            name:
              type:
                - string
                - "null"
        data:
          type: object
          required:
            - status
          description: >
            `status` is always present; the remaining fields vary by failure
            path — treat any field not listed here as additive.
          properties:
            status:
              type: string
              enum:
                - failed
            failure_reason:
              type: string
              description: Human-readable explanation of the failure.
            audience_id:
              type:
                - string
                - "null"
              description: Audience wrapper ID if one was created alongside the segment.
          additionalProperties: true
        links:
          type: object
          additionalProperties: true
          description: Reserved for future related-resource links. Currently always empty.
    DeliveryWebhookCompletedPayload:
      type: object
      description: >
        Canonical envelope posted to your webhook when a delivery export
        finishes and download URLs (or an equivalent destination confirmation)
        are available.
      required:
        - id
        - type
        - created_at
        - api_version
        - org_id
        - subject
        - data
        - links
      properties:
        id:
          type: string
          example: evt_1a2b3c4d5e6f7a8b9c0d1e2f
        type:
          type: string
          enum:
            - delivery.completed
        created_at:
          type: string
          format: date-time
        api_version:
          type: string
          example: 2026-08-06
        org_id:
          type: string
        subject:
          type: object
          description: Identifies the delivery this event is about.
          required:
            - kind
            - id
            - name
          properties:
            kind:
              type: string
              enum:
                - delivery
            id:
              type: string
            name:
              type:
                - string
                - "null"
        data:
          type: object
          required:
            - status
            - destination
          description: >
            `status` and `destination` are always present; the remaining fields
            vary by destination and delivery path — treat any field not listed
            here as additive.
          properties:
            status:
              type: string
              enum:
                - completed
            destination:
              type: string
              enum:
                - download
                - liveramp
                - narrative
            output_record_count:
              type: integer
            completed_at:
              type: string
              format: date-time
            output_format:
              type: string
            output_compression:
              type: string
            output_bytes:
              type: integer
            download_urls:
              type: array
              items:
                type: string
                format: uri
              description: Signed download URLs (valid 24 hours). Present for
                destination=download only.
          additionalProperties: true
        links:
          type: object
          additionalProperties: true
          description: Reserved for future related-resource links. Currently always empty.
    DeliveryWebhookFailedPayload:
      type: object
      description: |
        Canonical envelope posted to your webhook when a delivery export fails.
      required:
        - id
        - type
        - created_at
        - api_version
        - org_id
        - subject
        - data
        - links
      properties:
        id:
          type: string
          example: evt_1a2b3c4d5e6f7a8b9c0d1e2f
        type:
          type: string
          enum:
            - delivery.failed
        created_at:
          type: string
          format: date-time
        api_version:
          type: string
          example: 2026-08-06
        org_id:
          type: string
        subject:
          type: object
          description: Identifies the delivery this event is about.
          required:
            - kind
            - id
            - name
          properties:
            kind:
              type: string
              enum:
                - delivery
            id:
              type: string
            name:
              type:
                - string
                - "null"
        data:
          type: object
          required:
            - status
            - destination
            - failure_reason
          description: >
            `status`, `destination`, and `failure_reason` are always present;
            the remaining fields vary by delivery path — treat any field not
            listed here as additive.
          properties:
            status:
              type: string
              enum:
                - failed
            destination:
              type: string
              enum:
                - download
                - liveramp
                - narrative
            failure_reason:
              type: string
              description: Human-readable reason the delivery failed.
          additionalProperties: true
        links:
          type: object
          additionalProperties: true
          description: Reserved for future related-resource links. Currently always empty.
    A2AMessagePart:
      type: object
      description: A single content fragment within an A2A message.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - text
            - data
          description: Content type. "text" for plain text, "data" for structured JSON.
        text:
          type: string
          description: The text content (present when type is "text").
        data:
          type: object
          description: Structured JSON payload (present when type is "data").
          additionalProperties: true
    A2AMessage:
      type: object
      description: A message in an A2A task conversation.
      required:
        - role
        - parts
      properties:
        role:
          type: string
          enum:
            - user
            - agent
          description: Who authored this message.
        parts:
          type: array
          description: One or more content fragments composing the message.
          items:
            $ref: "#/components/schemas/A2AMessagePart"
          minItems: 1
    A2ATask:
      type: object
      required:
        - id
        - status
        - messages
        - created_at
        - updated_at
      description: Represents an asynchronous agent task. Submit via POST /a2a/tasks
        and poll via GET /a2a/tasks/{id} until status reaches a terminal state.
      properties:
        id:
          type: string
          format: uuid
          description: Unique task identifier.
        status:
          type: string
          enum:
            - submitted
            - working
            - completed
            - failed
            - cancelled
          description: Current task lifecycle state. submitted → working → completed |
            failed | cancelled
        result_message:
          $ref: "#/components/schemas/A2AMessage"
          description: The agent's final response message. Only present when status is
            "completed". Contains a parts array — each part has a type ("text"
            or "data") and the corresponding content.
        error:
          type: object
          description: Error details. Only present when status is "failed".
          properties:
            code:
              type: string
            message:
              type: string
        messages:
          type: array
          description: Full conversation history including user and agent turns.
          items:
            $ref: "#/components/schemas/A2AMessage"
        metadata:
          type: object
          description: Caller-supplied key/value pairs from the original task submission.
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
  responses:
    BadGateway:
      description: An upstream billing provider rejected or could not complete the
        operation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServiceUnavailable:
      description: A required billing provider partnership is temporarily unavailable.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ScopeRequired:
      description: >
        Token is valid but lacks the required scope for this endpoint. Check the
        endpoint description for the required scope (`discovery`, `purchase`, or
        `account`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            missing_scope:
              value:
                error: SCOPE_REQUIRED
                message: This endpoint requires the purchase scope.
    BadRequest:
      description: >
        Invalid request — malformed body, missing required attribute, or failed
        validation. See `error` and `message` for details.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            validation_error:
              value:
                error: Bad Request
                code: MISSING_SEGMENTS
                message: segment_ids is required for filter audiences.
    Unauthorized:
      description: >
        Missing or invalid Bearer token. Obtain one via POST /v1/auth/token.
        When a token was supplied but rejected, `code` distinguishes
        `TOKEN_EXPIRED` (the token's lifetime has passed — request a new one via
        POST /v1/auth/token and retry) from `TOKEN_INVALID` (malformed or
        revoked — re-authenticate).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            missing_token:
              value:
                error: "Unauthorized: Missing or invalid Authorization header"
            token_expired:
              value:
                error: Unauthorized
                code: TOKEN_EXPIRED
                message: Your session has expired. Please sign in again.
    Forbidden:
      description: >
        The authenticated organisation is suspended or otherwise forbidden from
        performing this operation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    PaymentRequired:
      description: >
        The request cannot proceed on billing grounds.
        `BILLING_INSUFFICIENT_BALANCE` applies only to prepay accounts and
        includes `required`, `available`, and `shortfall` in USD.
        `BILLING_POSTPAY_CEILING_EXCEEDED` applies to an account ceiling;
        `BILLING_CONSUMER_POSTPAY_CEILING_EXCEEDED` applies to an agency child's
        routed ceiling. Both ceiling responses include projected `accrued` and
        configured `ceiling` in USD.
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ErrorResponse"
              - type: object
                properties:
                  code:
                    type: string
                    enum:
                      - BILLING_INSUFFICIENT_BALANCE
                      - BILLING_POSTPAY_CEILING_EXCEEDED
                      - BILLING_CONSUMER_POSTPAY_CEILING_EXCEEDED
                  shortfall:
                    type: number
                  required:
                    type: number
                  available:
                    type: number
                  accrued:
                    type: number
                  ceiling:
                    type: number
          examples:
            insufficient_balance:
              value:
                error: Insufficient balance
                code: BILLING_INSUFFICIENT_BALANCE
                message: Insufficient effective balance
                required: 1
                available: 0.75
                shortfall: 0.25
            budget_ceiling_reached:
              value:
                error: Billing capacity unavailable
                code: BILLING_POSTPAY_CEILING_EXCEEDED
                message: Postpay ceiling would be exceeded
                accrued: 105
                ceiling: 100
    QueryExecutionError:
      description: >
        The underlying analytics query failed, or (`AGGREGATION_FLOOR_NOT_MET`)
        a non-discovery route encountered a result below the privacy floor.
        `message` is always a friendly, client-safe description — the raw
        backend error is never returned.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            aggregation_floor_not_met:
              value:
                error: Bad Gateway
                code: AGGREGATION_FLOOR_NOT_MET
                message: For privacy reasons, a minimum of 50 records is required to view this
                  information.
            table_missing:
              value:
                error: Bad Gateway
                code: BQ_TABLE_MISSING
                message: This data is missing — it may have expired or the original upload never
                  finished processing. Try re-uploading, or contact support if
                  this persists.
    AggregationFloorNotMet:
      description: The result is below the minimum population that can be returned
        while preserving privacy.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error: Privacy threshold not met
            code: AGGREGATION_FLOOR_NOT_MET
            message: For privacy reasons, a minimum of 50 records is required to view this
              information.
    NotFound:
      description: Resource not found or not accessible to the calling org.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            not_found:
              value:
                error: Audience not found
    Conflict:
      description: >
        Resource state conflicts with the request. For upload analysis this
        includes FILE_NOT_YET_UPLOADED.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    UnprocessableEntity:
      description: >
        Request is structurally valid but the resource is in a state that
        prevents the operation (e.g. expired, pending, or not yet uploaded). See
        `error` and `code` for the specific reason.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            audience_expired:
              value:
                error: Audience expired
                code: AUDIENCE_EXPIRED
                message: This audience version expired. Run a refresh to create a new
                  deliverable version.
            audience_pending:
              value:
                error: Audience not ready
                code: AUDIENCE_PENDING
                message: This audience is still being processed. Poll GET /v1/audiences/{id}
                  until status is active.
    InternalServerError:
      description: Unexpected server failure.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: >
        Request throttled. Two distinct conditions return 429: **Rate limit** —
        too many requests per minute for your tier (`error:
        rate_limit_exceeded`); retry after `retry_after_seconds`. **Compute
        ceiling** — org-level query-scan budget exhausted (`error:
        compute_limit_exceeded`); resets hourly or on plan upgrade.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            rate_limit:
              value:
                error: rate_limit_exceeded
                message: Rate limit of 60 requests/minute exceeded for tier 'starter'.
                retry_after_seconds: 45
            compute_limit:
              value:
                error: compute_limit_exceeded
                message: Org compute ceiling exceeded. Upgrade your plan or wait for the hourly
                  reset.
    OAuthError:
      description: >
        RFC 6749-shaped OAuth protocol error — `error` is a fixed
        machine-readable code and `error_description` is a human-readable
        detail. Deliberately NOT this API's usual `{error, code, message}` shape
        — this is the RFC's own error contract, used only by
        `/v1/oauth/authorize`, `/v1/oauth/token`, and `/v1/oauth/revoke`.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthErrorResponse"
security:
  - bearerAuth: []
tags:
  - name: Auth
    description: Token exchange
  - name: OAuth
    description: >
      OAuth 2.0 authorization server for confidential integrations and delegated
      REST, MCP, and A2A clients. Authenticate on behalf of one of your
      customers via the standard authorization-code flow with PKCE, then use the
      resulting access token exactly like an API-key-derived one.
  - name: Triggers
    description: >
      Durable, per-event-filtered webhook-trigger subscriptions (e.g. one per
      Zap) — distinct from the single implicit URL configured under the Webhooks
      tag / `/v1/settings/webhook`. Delivery is retried with exponential backoff
      and dead-lettered after repeated failures. An OAuth-authenticated caller
      manages only the subscriptions created by its own connection; an
      API-key/session caller with 'account' scope manages every subscription in
      the org.
  - name: Discovery
    description: Audience count and lookup (exploration)
  - name: Campaigns
    description: Named workspaces that group related audiences and deliveries under
      a single context
  - name: Segments
    description: Save and manage reusable filter definitions
  - name: Audiences
    description: Build, store, and manage named audience definitions
  - name: Deliveries
    description: Org-wide delivery history and status
  - name: Enrichment
    description: Attribute catalog, propensity models, synchronous micro-batch
      matching, and async file-based identity resolution
  - name: Workflows
    description: Monitor async audience pipeline progress (similarity and propensity
      subtypes). Poll GET /v1/workflows/{workflow_run_id} or respond to HITL
      gates via POST /v1/workflows/{workflow_run_id}/resume.
  - name: Account
    description: Balance, top-up, and subscription management
  - name: Settings
    description: Self-service API keys and org configuration
  - name: Webhooks
    description: >
      **Outbound webhook notifications** — the platform POSTs to your endpoint
      when async operations finish.


      **Events fired:**

      | Event | Trigger | |---|---| | `segment.ready` | Async segment build
      succeeded (matched, similarity, propensity) | | `segment.failed` | Async
      segment build failed | | `delivery.completed` | File export/download ready
      | | `delivery.failed` | File export/download failed |

      OAuth partnerships may additionally subscribe their own grant to
      `integration.enrichment.prepared`, `integration.enrichment.failed`,
      `integration.file.ready`, and `integration.file.failed`. Those callbacks
      are delivered only to the owning grant's subscription and never to the
      org-wide webhook.


      **URL precedence** — per-request `webhook_url` > org-configured URL
      (Settings) > no dispatch. If an individual API request includes
      `webhook_url`, that URL is used for that call only; the org-level URL is
      not called. Omitting `webhook_url` on a request falls back to the org URL.


      **Payload shape** — every dispatch (org-configured URL, per-request
      `webhook_url`, and durable subscriptions alike) sends the same canonical
      envelope: `{id, type, created_at, api_version, org_id, subject, data,
      links}`. `type` is the event name (e.g. `segment.ready`); `subject`
      identifies what the event is about (`{kind, id, name}`); `data` carries
      `status` plus event-specific fields — see each event below for its exact
      shape.


      **Signing** — every outbound dispatch is signed. The signing secret is
      platform-generated — you never supply one — and shown to you exactly once:
      in the response of `PUT /v1/settings/webhook` (the first time a webhook is
      configured) or `POST /v1/settings/webhook/secret` (rotate), or in the
      response of `POST /v1/webhook-subscriptions` for a durable subscription.
      Verifying is optional; every dispatch includes: ``` X-IA-Timestamp:
      <unix-seconds> X-IA-Signature: v1=<hmac-hex> ``` computed as
      `HMAC-SHA256(secret, "{timestamp}.{raw-request-body}")`. To verify: reject
      if `timestamp` is more than 300 seconds old, then recompute the HMAC over
      the exact raw request body bytes and compare with `crypto.timingSafeEqual`
      (or equivalent) — never compare against a re-serialized/re-parsed body,
      since re-serialization is not guaranteed to produce identical bytes.
  - name: MCP
    description: Model Context Protocol session management
  - name: A2A
    description: Agent-to-Agent task protocol
  - name: Partnerships
    description: Retained activity and reusable results produced through connected partners
paths:
  /v1/auth/token:
    post:
      summary: Exchange API key for an access token
      operationId: exchangeToken
      description: >
        Public endpoint — no token required. Exchanges an org API key
        (`cf_live_...`) for a short-lived Bearer access token. Include the
        returned `access_token` as `Authorization: Bearer <access_token>` on all
        subsequent protected requests. Suspended organisations and children of a
        suspended agency cannot exchange API keys.
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - api_key
              properties:
                api_key:
                  type: string
                  description: Organisation API key in format cf_live_...
      responses:
        "200":
          description: Access token issued
          content:
            application/json:
              schema:
                type: object
                required:
                  - access_token
                  - token_type
                  - expires_in
                properties:
                  access_token:
                    type: string
                    description: Bearer access token — include as Authorization header on protected
                      requests
                  token_type:
                    type: string
                    example: bearer
                  expires_in:
                    type: integer
                    description: Token lifetime in seconds
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /v1/oauth/authorize:
    get:
      summary: Begin an OAuth 2.0 authorization request
      operationId: oauthAuthorize
      description: >
        Public — no bearer token. Redirect your user's browser here to start the
        standard OAuth 2.0 authorization-code flow with PKCE (RFC 6749 + RFC
        7636). `code_challenge_method` must be `S256` — `plain` is rejected (RFC
        9700). On success, the browser is eventually redirected back to your
        `redirect_uri` with a `code` (and your `state`, if you sent one) to
        exchange at `POST /v1/oauth/token`. On a validation failure, the browser
        is redirected back with `error` and `error_description` instead (RFC
        6749 §4.1.2.1) — except an unknown `client_id` or an unregistered
        `redirect_uri`, which return 400 JSON directly rather than a redirect,
        since there is no verified address yet to redirect to.
      tags:
        - OAuth
      security: []
      parameters:
        - name: response_type
          in: query
          required: true
          schema:
            type: string
            enum:
              - code
        - name: client_id
          in: query
          required: true
          schema:
            type: string
        - name: redirect_uri
          in: query
          required: true
          schema:
            type: string
            format: uri
        - name: scope
          in: query
          required: false
          schema:
            type: string
          description: Space-delimited scopes (`discovery`, `purchase`). Defaults to
            `discovery`.
        - name: resource
          in: query
          required: false
          schema:
            type: string
            format: uri
          description: >
            RFC 8707 resource URI. Required for public MCP/A2A clients. A
            pre-registered confidential client that omits it receives a
            REST-bound token.
        - name: state
          in: query
          required: false
          schema:
            type: string
          description: Opaque value echoed back on both the success and error redirects.
        - name: code_challenge
          in: query
          required: true
          schema:
            type: string
        - name: code_challenge_method
          in: query
          required: true
          schema:
            type: string
            enum:
              - S256
      responses:
        "302":
          description: >
            Redirects to the consent page on success, or back to your
            `redirect_uri` with `error`/`error_description` (and `state`, if you
            sent one) on a validation failure that occurred after `redirect_uri`
            was verified.
        "400":
          $ref: "#/components/responses/OAuthError"
  /v1/oauth/token:
    post:
      summary: Exchange an authorization code or refresh token for an access token
      operationId: oauthToken
      description: >
        Confidential clients authenticate per RFC 6749 §3.2. Public clients send
        `client_id` with no secret and rely on PKCE or their rotating refresh
        token. Accepts `application/x-www-form-urlencoded` or
        `application/json`. Send your `client_id`/`client_secret` via HTTP Basic
        (`Authorization: Basic base64(client_id:client_secret)`) or as body
        fields. Public clients use `token_endpoint_auth_method=none`.

        `grant_type=authorization_code` requires `code`, the exact same
        `redirect_uri` you sent to `/authorize`, and `code_verifier` (the PKCE
        secret whose SHA-256 hash matches the `code_challenge` you sent to
        `/authorize`).

        `grant_type=refresh_token` requires `refresh_token` and rotates it —
        store the `refresh_token` returned in the response and discard the one
        you presented; it is now rejected if presented again.
      tags:
        - OAuth
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/OAuthTokenRequest"
          application/json:
            schema:
              $ref: "#/components/schemas/OAuthTokenRequest"
      responses:
        "200":
          description: A fresh access token and a rotated refresh token, for either grant
            type.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthTokenResponse"
        "400":
          $ref: "#/components/responses/OAuthError"
        "401":
          $ref: "#/components/responses/OAuthError"
  /v1/oauth/revoke:
    post:
      summary: Revoke a token (RFC 7009)
      operationId: oauthRevoke
      description: >
        Client-authenticated, same credentials as `/v1/oauth/token`. Call this
        when your user disconnects the partnership. Always returns 200 with an
        empty body regardless of whether the presented token was valid, except
        when client authentication itself fails — there is nothing to
        distinguish for a caller who might be probing for valid tokens (RFC 7009
        §2.2).
      tags:
        - OAuth
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/OAuthRevokeRequest"
          application/json:
            schema:
              $ref: "#/components/schemas/OAuthRevokeRequest"
      responses:
        "200":
          description: Returned once the client itself authenticated, regardless of
            whether the presented token was valid.
          content:
            application/json:
              schema:
                type: object
        "401":
          $ref: "#/components/responses/OAuthError"
  /v1/oauth/register:
    post:
      summary: Register a public OAuth client (RFC 7591 fallback)
      operationId: oauthRegisterClient
      description: >
        Public and rate-limited. Registers a public PKCE client for MCP/A2A
        gateways that do not support Client ID Metadata Documents. No client
        secret is issued.
      tags:
        - OAuth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OAuthClientRegistrationRequest"
      responses:
        "201":
          description: Public client registration.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientRegistrationResponse"
        "400":
          $ref: "#/components/responses/OAuthError"
        "401":
          $ref: "#/components/responses/OAuthError"
  /.well-known/oauth-authorization-server:
    get:
      summary: OAuth 2.0 authorization server metadata (RFC 8414)
      operationId: oauthMetadata
      description: Public, static metadata describing this authorization server's
        endpoints and capabilities.
      tags:
        - OAuth
      security: []
      responses:
        "200":
          description: Authorization server metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthServerMetadata"
  /.well-known/oauth-protected-resource:
    get:
      summary: REST protected-resource metadata (RFC 9728)
      operationId: oauthRestProtectedResourceMetadata
      tags:
        - OAuth
      security: []
      responses:
        "200":
          description: REST resource metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthProtectedResourceMetadata"
  /.well-known/oauth-protected-resource/mcp:
    get:
      summary: MCP protected-resource metadata (RFC 9728)
      operationId: oauthMcpProtectedResourceMetadata
      tags:
        - OAuth
      security: []
      responses:
        "200":
          description: MCP resource metadata used by OAuth-aware clients.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthProtectedResourceMetadata"
  /.well-known/oauth-protected-resource/a2a:
    get:
      summary: A2A protected-resource metadata (RFC 9728)
      operationId: oauthA2aProtectedResourceMetadata
      tags:
        - OAuth
      security: []
      responses:
        "200":
          description: A2A resource metadata advertised by the Agent Card.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthProtectedResourceMetadata"
  /v1/webhook-subscriptions:
    post:
      summary: Create a webhook-trigger subscription
      operationId: createWebhookSubscription
      description: >
        Registers a durable trigger subscription — delivery is retried with
        exponential backoff and dead-lettered after repeated failures (a 410
        response from `target_url` dead-letters immediately and revokes the
        subscription — this is how Zapier signals "this Zap was turned off").
        Requires 'account' scope, or an OAuth access token — in the OAuth case,
        this subscription is bound to your connection automatically. Capped at
        25 active subscriptions per org.
      tags:
        - Triggers
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - target_url
                - events
              properties:
                target_url:
                  type: string
                  format: uri
                  description: Must be HTTPS and resolve to a public, non-internal address —
                    validated at creation and re-validated before every delivery
                    attempt.
                events:
                  type: array
                  minItems: 1
                  items:
                    $ref: "#/components/schemas/NotifiableEventType"
      responses:
        "201":
          description: Subscription created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionCreated"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          description: The org already has 25 active subscriptions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    get:
      summary: List webhook-trigger subscriptions
      operationId: listWebhookSubscriptions
      description: >
        Requires 'account' scope, or an OAuth access token — in the OAuth case,
        this list is scoped to subscriptions created by your own connection
        only.
      tags:
        - Triggers
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Subscriptions visible to the caller, most recent first.
          content:
            application/json:
              schema:
                type: object
                required:
                  - subscriptions
                properties:
                  subscriptions:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookSubscription"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/webhook-subscriptions/{id}:
    delete:
      summary: Revoke a webhook-trigger subscription
      operationId: revokeWebhookSubscription
      description: >
        Requires 'account' scope, or an OAuth access token. Idempotent —
        revoking an already-revoked subscription still returns 204. An OAuth
        caller may only revoke a subscription bound to its own connection; any
        other subscription id returns 404 rather than 403, so existence is never
        leaked.
      tags:
        - Triggers
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Subscription revoked (or was already revoked).
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/schema/fields:
    get:
      summary: Discover available filter attributes
      operationId: getSchemaFields
      description: >
        Returns the Attribute Catalog — every attribute you can use in the
        `filters` array when calling the count, lookup, crosstab, and overlap
        endpoints. For each attribute you'll get its data type, a plain-English
        description, a suggested UI widget, and (for categorical attributes) the
        allowed values. Use this to build dynamic filter UIs or to validate
        attribute names before submitting a query.

        Only filterable attributes are returned — there's no parameter to widen
        this set. The response is cached and refreshed nightly. Requires
        'discovery' scope.
      tags:
        - Discovery
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Attribute Catalog.
          content:
            application/json:
              schema:
                type: object
                properties:
                  fields:
                    type: array
                    items:
                      type: object
                      properties:
                        field_name:
                          type: string
                          example: gender
                        table_name:
                          type: string
                          example: demographics.consumers_v2
                        bq_type:
                          type: string
                          example: STRING
                        description:
                          type: string
                          description: Plain-English description of the attribute.
                          example: Age bracket of the consumer (e.g. 25-34)
                        filterable:
                          type: boolean
                        operators:
                          type: array
                          items:
                            type: string
                          example:
                            - =
                            - "!="
                            - IN
                            - NOT IN
                        ui_hint:
                          type:
                            - string
                            - "null"
                          example: multi_select
                        ui_label:
                          type: string
                          example: Gender
                        enum_values:
                          type:
                            - array
                            - "null"
                          items:
                            type: string
                          example:
                            - 18-24
                            - 25-34
                            - 35-44
                            - 45-54
                            - 55+
                        value_labels:
                          oneOf:
                            - type: object
                              additionalProperties:
                                type: string
                            - type: "null"
                          description: Maps raw stored codes to human-readable labels (e.g.
                            {"M":"Male","F":"Female"}).
                        is_pii:
                          type: boolean
                        groupable:
                          type: boolean
                        anchor:
                          type: boolean
                        data_level:
                          type:
                            - string
                            - "null"
                        category_display_name:
                          type:
                            - string
                            - "null"
                          example: Demographics & Household
                        category:
                          type: string
                      required:
                        - field_name
                        - table_name
                        - bq_type
                        - description
                        - filterable
                        - operators
                        - ui_hint
                        - ui_label
                        - enum_values
                        - value_labels
                        - is_pii
                        - groupable
                        - anchor
                        - data_level
                        - category_display_name
                  cached_at:
                    type: string
                    format: date-time
                required:
                  - fields
                  - cached_at
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/discovery/count:
    post:
      summary: Count records matching criteria
      operationId: discoveryCount
      description: >
        Returns the number of records matching the given criteria. Counts the
        bytes scanned toward your organization's compute limit. Requires
        'discovery' scope.

        Provide exactly one of: `filters` (flat array), `filter_groups`
        (compound AND/OR groups), `segment_id` (a saved segment), `audience_id`
        (a saved audience), or `segment_ids` + `set_logic` (combine multiple
        segments). `filters` and `filter_groups` count as the same input — if
        both are sent, `filter_groups` wins. Sending more than one input returns
        400 `AMBIGUOUS_INPUT`; sending none returns 400 `NO_INPUT_SPECIFIED`.

        Count, lookup, crosstab, and overlap all preserve full group/combinator
        structure: each included segment's own AND/OR groups are evaluated
        intact, and an audience's excluded segments are always subtracted (AND
        NOT), regardless of `set_logic`.
      tags:
        - Discovery
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                filters:
                  type: array
                  description: Flat filter array (recommended).
                  items:
                    $ref: "#/components/schemas/Filter"
                filter_groups:
                  type: array
                  description: >
                    Compound boolean filter groups (alternative to `filters`).
                    Each group has a `combinator` (AND/OR) and a `filters`
                    array.
                  items:
                    $ref: "#/components/schemas/FilterGroup"
                segment_id:
                  type: string
                  description: ID of a saved segment to count.
                audience_id:
                  type: string
                  description: ID of a saved audience to count.
                segment_ids:
                  type: array
                  items:
                    type: string
                  description: IDs of multiple segments to combine (requires `set_logic`).
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                  description: >
                    How to combine segment_ids: union = match ANY segment (OR);
                    intersection = match EVERY segment (AND).
                excluded_segment_ids:
                  type: array
                  items:
                    type: string
                  description: >
                    Ad-hoc "A minus B" preview — segments to subtract from the
                    `segment_ids` composition, without persisting an audience.
                    Only meaningful alongside `segment_ids`; do not send
                    `set_logic: 'exclusion'` (invalid) — exclusion is always
                    modeled via this separate param.
                group_by:
                  type: string
                  description: Optional attribute to group counts by (e.g. 'state', 'gender').
      responses:
        "200":
          description: Record count result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  bytes_scanned:
                    type: integer
                  estimated_cost_usd:
                    type: number
                  matched_count:
                    type: integer
                    description: >
                      Only present when the resolved composition includes at
                      least one included matched-subtype segment — the subset of
                      `count` that came from an identity-resolution match.
                  groups:
                    type: array
                    description: |
                      Present when `group_by` is specified.
                    items:
                      type: object
                      properties:
                        group_value:
                          type: string
                        count:
                          type: integer
                      required:
                        - group_value
                        - count
                    example:
                      - group_value: California
                        count: 1200000
                      - group_value: Texas
                        count: 980000
                  breakdowns:
                    type: object
                    description: >
                      Present by default (unless `group_by` is set) when the
                      resolved composition supports them — a single filter-based
                      segment/audience or raw filters. Not yet available for
                      non-filter or mixed-subtype compositions.
                    properties:
                      age:
                        type: array
                        items:
                          type: object
                          properties:
                            bucket:
                              type: string
                            count:
                              type: integer
                      gender:
                        type: array
                        items: &a1
                          type: object
                          properties:
                            value:
                              type: string
                            bucket:
                              type: string
                            count:
                              type: integer
                      geography:
                        type: array
                        items: *a1
                      behavioral:
                        type: array
                        items: *a1
                      financial:
                        type: array
                        items: *a1
                  warnings:
                    type: array
                    items:
                      type: string
                    description: >
                      Present only when the resolved segment_id/segment_ids/
                      audience_id composition includes a non-filter or mixed
                      subtype AND `group_by`/breakdowns were requested — those
                      are not yet available for non-filter or mixed-subtype
                      compositions, so the count is still returned but without
                      the requested grouping.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "422":
          $ref: "#/components/responses/AggregationFloorNotMet"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/discovery/lookup:
    post:
      summary: Return masked sample rows matching criteria
      operationId: discoveryLookup
      description: >
        Returns up to 25 masked sample rows matching the criteria. PII
        attributes are masked or omitted. Requires 'discovery' scope.

        Provide exactly one of: `filters`, `filter_groups`, `segment_id`,
        `audience_id`, or `segment_ids` + `set_logic` (same input shapes as
        /v1/discovery/count). Sending more than one returns 400
        `AMBIGUOUS_INPUT`; sending none returns 400 `NO_INPUT_SPECIFIED`.

        If you pass a segment, audience, or filter groups, full group/combinator
        structure is preserved — each segment's own AND/OR groups are evaluated
        intact, multiple composed segments are combined by `set_logic`, and an
        audience's excluded segments are always subtracted (AND NOT), regardless
        of `set_logic`.
      tags:
        - Discovery
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                filters:
                  type: array
                  description: Flat filter array (recommended).
                  items:
                    $ref: "#/components/schemas/Filter"
                filter_groups:
                  type: array
                  description: Compound boolean filter groups.
                  items:
                    $ref: "#/components/schemas/FilterGroup"
                segment_id:
                  type: string
                  description: ID of a saved segment to sample from.
                audience_id:
                  type: string
                  description: ID of a saved audience to sample from.
                segment_ids:
                  type: array
                  items:
                    type: string
                  description: IDs of multiple segments to combine (requires `set_logic`).
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                  description: >
                    How to combine segment_ids: union = match ANY segment (OR);
                    intersection = match EVERY segment (AND).
                excluded_segment_ids:
                  type: array
                  items:
                    type: string
                  description: >
                    Ad-hoc "A minus B" preview — segments to subtract from the
                    `segment_ids` composition, without persisting an audience.
                    Only meaningful alongside `segment_ids`; do not send
                    `set_logic: 'exclusion'` (invalid).
                limit:
                  type: integer
                  minimum: 1
                  maximum: 25
                  default: 10
      responses:
        "200":
          description: Sample rows.
          content:
            application/json:
              schema:
                type: object
                properties:
                  rows:
                    type: array
                    description: >
                      Up to `limit` anonymised sample rows. PII attributes
                      (email, phone, address) are masked or omitted.
                      `iag_person_id` is always null.
                    items:
                      type: object
                      additionalProperties:
                        type: string
                  total_matched:
                    type: integer
                    description: Total records matching the criteria (may exceed rows.length, which
                      is capped at `limit`).
                  bytes_scanned:
                    type: integer
                  estimated_cost_usd:
                    type: number
                  available_fields:
                    type: array
                    items:
                      type: string
                    description: Field names present on the returned rows (after PII
                      masking/omission).
                required:
                  - rows
                  - total_matched
                  - bytes_scanned
                  - estimated_cost_usd
                  - available_fields
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "422":
          $ref: "#/components/responses/AggregationFloorNotMet"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/discovery/crosstab:
    post:
      summary: Cross-tabulate two dimensions across a population
      operationId: discoveryCrosstab
      description: >
        Returns a pivot table of counts for two categorical dimensions (e.g.
        state × gender). Requires 'discovery' scope.

        Provide exactly one of: `filters`, `filter_groups`, `segment_id`,
        `audience_id`, or `segment_ids` + `set_logic` to define the population
        (same input shapes as /v1/discovery/count). Sending more than one
        returns 400 `AMBIGUOUS_INPUT`; sending none returns 400
        `NO_INPUT_SPECIFIED`. A 400 `SAME_FIELD` is returned if `row_field`
        equals `col_field`.

        If you pass a segment, audience, or filter groups, full group/combinator
        structure is preserved — each segment's own AND/OR groups are evaluated
        intact, multiple composed segments are combined by `set_logic`, and an
        audience's excluded segments are always subtracted (AND NOT), regardless
        of `set_logic`.
      tags:
        - Discovery
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - row_field
                - col_field
              properties:
                filters:
                  type: array
                  description: Flat filter array defining the population.
                  items:
                    $ref: "#/components/schemas/Filter"
                filter_groups:
                  type: array
                  description: Compound boolean filter groups (alternative to `filters`).
                  items:
                    $ref: "#/components/schemas/FilterGroup"
                segment_id:
                  type: string
                  description: ID of a saved segment defining the population.
                audience_id:
                  type: string
                  description: ID of a saved audience defining the population.
                segment_ids:
                  type: array
                  items:
                    type: string
                  description: IDs of multiple segments to combine (requires `set_logic`).
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                  description: >
                    How to combine segment_ids: union = match ANY segment (OR);
                    intersection = match EVERY segment (AND).
                excluded_segment_ids:
                  type: array
                  items:
                    type: string
                  description: >
                    Ad-hoc "A minus B" preview — segments to subtract from the
                    `segment_ids` composition, without persisting an audience.
                    Only meaningful alongside `segment_ids`.
                row_field:
                  type: string
                  minLength: 1
                  maxLength: 64
                  description: Attribute for the row dimension (e.g. "state").
                col_field:
                  type: string
                  minLength: 1
                  maxLength: 64
                  description: Attribute for the column dimension (e.g. "gender"). Must differ
                    from row_field.
                row_label:
                  type: string
                  maxLength: 80
                  description: Human-readable row label (defaults to row_field).
                col_label:
                  type: string
                  maxLength: 80
                  description: Human-readable column label (defaults to col_field).
                max_rows:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 20
                  description: Max distinct row values, ordered by total count desc.
                max_cols:
                  type: integer
                  minimum: 1
                  maximum: 20
                  default: 10
                  description: Max distinct column values, ordered by total count desc.
            example:
              segment_id: seg_1a2b3c
              row_field: state
              col_field: gender
              row_label: State
              col_label: Gender
              max_rows: 10
      responses:
        "200":
          description: Pivot table result.
          content:
            application/json:
              schema:
                type: object
                required:
                  - row_field
                  - col_field
                  - row_label
                  - col_label
                  - rows
                  - columns
                  - cells
                  - total_count
                properties:
                  row_field:
                    type: string
                  col_field:
                    type: string
                  row_label:
                    type: string
                    description: Echoes row_label (defaults to row_field).
                  col_label:
                    type: string
                    description: Echoes col_label (defaults to col_field).
                  rows:
                    type: array
                    description: Distinct row values, ordered by total count desc.
                    items:
                      type: string
                  columns:
                    type: array
                    description: Distinct column values, ordered by total count desc.
                    items:
                      type: string
                  cells:
                    type: object
                    description: cells[rowValue][colValue] = record count.
                    additionalProperties:
                      type: object
                      additionalProperties:
                        type: integer
                  total_count:
                    type: integer
                    description: Total records matching the population input.
              example:
                row_field: state
                col_field: gender
                row_label: State
                col_label: Gender
                rows:
                  - CA
                  - TX
                columns:
                  - F
                  - M
                cells:
                  CA:
                    F: 610230
                    M: 588112
                  TX:
                    F: 402881
                    M: 399124
                total_count: 2000347
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "422":
          $ref: "#/components/responses/AggregationFloorNotMet"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/discovery/overlap:
    post:
      summary: Calculate record overlap between two criteria sets
      operationId: discoveryOverlap
      description: >
        Computes the number and percentage of records that appear in both of two
        criteria sets, plus side-by-side demographic deltas. Requires
        'discovery' scope.

        Each side (`a` and `b`) independently accepts exactly one discovery
        input shape (`filters`, `filter_groups`, `segment_id`, `audience_id`, or
        `segment_ids` + `set_logic`) — the legacy flat `audience_id_a` /
        `audience_id_b` fields are also still accepted. A side that provides
        more than one shape, or combines the structured object with its legacy
        field, returns 400 `AMBIGUOUS_INPUT`. A side that provides nothing
        returns 400 `NO_INPUT_SPECIFIED`.

        Each side preserves full group/combinator structure independently: if a
        side is a segment, audience, or filter groups, each segment's own AND/OR
        groups are evaluated intact, multiple composed segments are combined by
        `set_logic`, and an audience's excluded segments are always subtracted
        (AND NOT), regardless of `set_logic` — before the two sides are joined
        for the overlap count.
      tags:
        - Discovery
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                a:
                  $ref: "#/components/schemas/DiscoveryInput"
                  description: Side A. Mutually exclusive with `audience_id_a`.
                b:
                  $ref: "#/components/schemas/DiscoveryInput"
                  description: Side B. Mutually exclusive with `audience_id_b`.
                audience_id_a:
                  type: string
                  description: (Legacy) First audience ID. Mutually exclusive with `a`.
                audience_id_b:
                  type: string
                  description: (Legacy) Second audience ID. Mutually exclusive with `b`.
            example:
              a:
                segment_id: seg_1a2b3c
              b:
                audience_id: aud_9z8y7x
      responses:
        "200":
          description: Overlap result with demographic deltas.
          content:
            application/json:
              schema:
                type: object
                required:
                  - a_name
                  - b_name
                  - a_count
                  - b_count
                  - overlap_count
                  - overlap_pct
                  - demographic_deltas
                properties:
                  a_name:
                    type: string
                  b_name:
                    type: string
                  a_count:
                    type: integer
                  b_count:
                    type: integer
                  overlap_count:
                    type: integer
                  overlap_pct:
                    type: number
                    description: Overlap as a percentage of the smaller side.
                  demographic_deltas:
                    type: array
                    description: >
                      Side-by-side demographic comparison. Each entry: {metric,
                      label, a, b, delta}.
                    items:
                      type: object
                      required:
                        - metric
                        - label
                        - a
                        - b
                        - delta
                      properties:
                        metric:
                          type: string
                        label:
                          type: string
                        a:
                          type:
                            - number
                            - "null"
                        b:
                          type:
                            - number
                            - "null"
                        delta:
                          type:
                            - number
                            - "null"
                  distribution_a:
                    type: array
                    description: Optional paired distribution buckets for side A.
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  distribution_b:
                    type: array
                    description: Optional paired distribution buckets for side B.
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  distribution_field:
                    type: string
                    description: Attribute the paired distributions are bucketed on.
                  distribution_label:
                    type: string
                    description: Human-readable label for the distribution attribute.
                  age_distribution_a:
                    type: array
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  age_distribution_b:
                    type: array
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  gender_distribution_a:
                    type: array
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  gender_distribution_b:
                    type: array
                    items:
                      $ref: "#/components/schemas/DistributionBucket"
                  warnings:
                    type: array
                    items:
                      type: string
                    description: >
                      Present only when at least one side resolves to a
                      non-filter or mixed-subtype composition. Two cases: "One
                      or both sides had no composable segments" when a side's
                      composition is empty (counts are all 0); or "Demographic
                      breakdowns are not yet available for non-filter or
                      mixed-subtype compositions" when counts/ overlap were
                      computed directly but demographic_deltas could not be
                      (returned as an empty array in that case).
              example:
                a_name: CA Homeowners
                b_name: TX Families
                a_count: 1204551
                b_count: 988102
                overlap_count: 51233
                overlap_pct: 5.2
                demographic_deltas:
                  - metric: median_age
                    label: Median age
                    a: 44
                    b: 39
                    delta: -5
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "422":
          $ref: "#/components/responses/AggregationFloorNotMet"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/quote:
    post:
      summary: Delivery quote
      operationId: quoteDelivery
      description: >
        Returns a price estimate for a proposed delivery without executing it.
        Provide the audience ID, destination, and optionally `field_list` or
        `template_id` (mutually exclusive) to receive a structured cost
        breakdown and credit balance information. If neither is provided, the
        Standard IAG attribute set is used. No commitment is made and nothing is
        stored. Download is the currently active destination; LiveRamp and
        Narrative return 503 before a quote or billable record is created until
        their partner pushes are implemented.

        Returns two cost figures: `cost_estimate` is license-aware (identities
        this audience already holds an active 12-month license for elsewhere are
        excluded from the billable count) and `max_cost_estimate` is the
        worst-case ceiling assuming no identity is pre-licensed. See
        `license_pricing.status` for whether `cost_estimate` reflects a live or
        cached computation, or degraded non-blockingly to the max figure.
              Requires 'discovery' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audience_id
                - destination
              properties:
                audience_id:
                  type: string
                  description: |
                    ID of the audience to quote a delivery for.
                destination:
                  type: string
                  enum:
                    - download
                    - liveramp
                    - narrative
                  description: >
                    Intended destination. `liveramp` and `narrative` currently
                    return `DELIVERY_DESTINATION_UNAVAILABLE` without billing.
                template_id:
                  type:
                    - string
                    - "null"
                  description: >
                    Enrichment template ID. Only `standard_iag` is valid.
                    Mutually exclusive with `field_list`. If neither is
                    provided, the Standard IAG attribute set (all attributes
                    with `product_usage` containing `audience`) is used.
                field_list:
                  type:
                    - array
                    - "null"
                  items:
                    type: string
                  description: >
                    Explicit attribute list — every attribute must be a valid
                    audience attribute (see GET /v1/catalog/fields). Mutually
                    exclusive with `template_id`. If neither is provided, the
                    Standard IAG attribute set is used.
                include_unmatched:
                  type: boolean
                  default: true
                  description: Include unmatched input rows in matched-audience output.
                output_format:
                  type: string
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  default: csv
                  description: >
                    Artifact format being quoted, independent of matched-input
                    format.
                output_compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    gzip is supported for csv/json/jsonl. Avro uses native
                    DEFLATE and rejects outer gzip.
      responses:
        "200":
          description: |
            Price quote — no commitment made.
          content:
            application/json:
              schema:
                type: object
                required:
                  - audience_id
                  - subtype
                  - destination
                  - template_id
                  - field_list
                  - output_format
                  - output_compression
                  - already_delivered
                  - quote_id
                  - cost_estimate
                  - max_cost_estimate
                  - license_pricing
                  - record_count
                  - match_count
                  - billing_model
                  - available_balance
                  - sufficient_balance
                  - shortfall
                  - budget_ceiling
                  - accrued_balance
                properties:
                  audience_id:
                    type: string
                  subtype:
                    type: string
                    enum:
                      - filter
                      - matched
                      - similarity
                      - propensity
                  destination:
                    type: string
                  template_id:
                    type:
                      - string
                      - "null"
                  field_list:
                    type:
                      - array
                      - "null"
                    items:
                      type: string
                  output_format:
                    type: string
                    enum:
                      - csv
                      - avro
                      - json
                      - jsonl
                  output_compression:
                    type: string
                    enum:
                      - none
                      - gzip
                  already_delivered:
                    type: boolean
                    description: >
                      `true` if this billing identity (audience composition and
                      segment revisions, resolved fields, unmatched-row policy,
                      and destination) has already been delivered. Artifact
                      format/compression are deliberately excluded, so changing
                      only those axes permits a free re-export.
                  quote_id:
                    type:
                      - string
                      - "null"
                    description: >
                      ID of the persisted quote backing this estimate. `null`
                      when `already_delivered` is `true` (a free re-delivery has
                      no pending cost, so nothing is persisted).
                  cost_estimate:
                    allOf:
                      - $ref: "#/components/schemas/CostEstimate"
                    description: >
                      The license-aware, expected-to-be-charged estimate —
                      reflects any active licenses this audience's identities
                      already hold elsewhere (see license_pricing.status).
                      Equals max_cost_estimate exactly when
                      license_pricing.status is unavailable.
                  max_cost_estimate:
                    allOf:
                      - $ref: "#/components/schemas/CostEstimate"
                    description: >
                      The worst-case ceiling, computed as if no identity in this
                      audience were already licensed anywhere. Always present
                      and always the same value regardless of
                      license_pricing.status — use this for a stable upper
                      bound.
                  license_pricing:
                    $ref: "#/components/schemas/LicensePricing"
                  record_count:
                    type: integer
                    description: |
                      Current audience size.
                  match_count:
                    type:
                      - integer
                      - "null"
                    description: >
                      matched subtype only — resolved rows from most recent
                      delivery.
                  billing_model:
                    type: string
                    enum:
                      - prepay
                      - postpay
                    description: >
                      Resolved from the org's own billing_model, or 'postpay'
                      for parent_billed (agency) orgs. Determines which of
                      available_balance/shortfall vs.
                      budget_ceiling/accrued_balance is populated below.
                  available_balance:
                    type:
                      - number
                      - "null"
                    description: >
                      Prepay only — effective credit balance after holds. Null
                      for postpay/agency orgs, which have no credit balance.
                  sufficient_balance:
                    type: boolean
                    description: >
                      Prepay: whether available_balance ≥ total_cost.
                      Postpay/agency: whether this delivery would stay within
                      the org's/agency's optional budget ceiling (always true
                      when no ceiling is set).
                  shortfall:
                    type:
                      - number
                      - "null"
                    description: >
                      Prepay only — credits needed beyond current balance (0
                      when sufficient). Null for postpay/agency.
                  budget_ceiling:
                    type:
                      - number
                      - "null"
                    description: >
                      Postpay/agency only — the org's configured budget ceiling
                      in USD. Null if unset (unlimited) or for prepay.
                  accrued_balance:
                    type:
                      - number
                      - "null"
                    description: >
                      Postpay/agency only — accrued USD spend so far this cycle.
                      Null for prepay.
              example:
                audience_id: aud_abc123
                subtype: filter
                destination: download
                template_id: standard_iag
                field_list: null
                output_format: csv
                output_compression: none
                already_delivered: false
                quote_id: q_abc123
                cost_estimate:
                  base_cost: 0.08
                  match_cost: 0
                  field_cost: 0.04
                  destination_cost: 0
                  total_cost: 0.12
                  unit_price: 0.0015
                  billing_count: 80000
                max_cost_estimate:
                  base_cost: 0.08
                  match_cost: 0
                  field_cost: 0.04
                  destination_cost: 0
                  total_cost: 0.12
                  unit_price: 0.0015
                  billing_count: 80000
                license_pricing:
                  status: live
                  evaluated_at: 2026-08-21T12:00:00.000Z
                  already_licensed_count: 0
                record_count: 80000
                match_count: null
                billing_model: prepay
                available_balance: 5
                sufficient_balance: true
                shortfall: 0
                budget_ceiling: null
                accrued_balance: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/campaigns:
    post:
      summary: Create a campaign
      operationId: createCampaign
      description: >
        Creates a new campaign workspace. A **Campaign** is a named container
        that groups related audiences and deliveries under a single context —
        useful for organising a body of work such as a quarterly prospecting
        initiative or a product-launch outreach.


        **Campaign association is optional.** Audiences, file-match jobs, and
        deliveries all work independently without a campaign. Use campaigns when
        you want to track related work together — e.g. filtering `GET
        /v1/operations` to `campaign_id` for a specific initiative's activity.


        After creation, associate audiences with the campaign by:

        - Passing `campaign_id` to `POST /v1/audiences` or `POST /v1/match/file`
        at creation time (auto-links on creation).

        - Calling `POST /v1/campaigns/{id}/audiences/{audience_id}` to link an
        existing audience.


        Requires 'purchase' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: |
                    Display name for the campaign.
                description:
                  type: string
                  maxLength: 1000
                  description: Optional campaign brief, intended market, goals, and constraints.
            examples:
              basic:
                summary: Create a campaign
                value:
                  name: Q3 2026 Prospecting
      responses:
        "201":
          description: |
            Campaign created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    get:
      summary: List campaigns
      operationId: listCampaigns
      description: >
        Returns all campaigns for the org, ordered by most recently updated.
        Filter by status to scope to active or archived campaigns. Requires
        'discovery' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - active
              - archived
          description: |
            Filter by campaign status. Omit to return all campaigns.
      responses:
        "200":
          description: |
            Campaign list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaigns:
                    type: array
                    items:
                      $ref: "#/components/schemas/Campaign"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/campaigns/{id}:
    get:
      summary: Get a campaign
      operationId: getCampaign
      description: >
        Returns full campaign details including an expanded `audiences` array
        with resolved segment names and record counts for each linked audience.
              Requires 'discovery' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Campaign ID returned by POST /v1/campaigns.
      responses:
        "200":
          description: |
            Campaign with expanded audience list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      summary: Update a campaign
      operationId: updateCampaign
      description: >
        Rename a campaign, revise its brief, or change its status. Supported
        status values: `archived` (soft-delete) and `active` (restore from
        archived).
              Requires 'purchase' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 120
                  description: |
                    New display name.
                description:
                  type: string
                  maxLength: 1000
                  description: Campaign brief; send an empty string to clear it.
                status:
                  type: string
                  enum:
                    - archived
                    - active
                  description: >
                    `archived` — soft-deletes the campaign (excluded from list
                    by default). `active` — restores a previously archived
                    campaign.
      responses:
        "200":
          description: |
            Updated campaign.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      summary: Archive a campaign
      operationId: archiveCampaign
      description: >
        Soft-archives the campaign by setting its status to `archived`. The
        campaign is not deleted and can be retrieved by listing with
        `?status=archived`. Idempotent — archiving an already-archived campaign
        returns 204.
              Requires 'purchase' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: |
            Campaign archived.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/campaigns/{id}/audiences/{audience_id}:
    post:
      summary: Link an audience to a campaign
      operationId: linkAudienceToCampaign
      description: >
        Associates an existing audience with a campaign. An audience can belong
        to at most one campaign at a time (exclusive ownership). Returns `409`
        if the audience is already linked to a **different** campaign — unlink
        it first. Idempotent: linking an already-linked audience to the **same**
        campaign returns `204` without error.


        For new audiences, it is simpler to pass `campaign_id` directly to `POST
        /v1/audiences` or `POST /v1/match/file` to auto-link at creation time.
              Requires 'purchase' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Campaign ID.
        - name: audience_id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience ID to link.
      responses:
        "204":
          description: |
            Audience linked (or was already linked to this campaign).
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: |
            Audience is already linked to a different campaign.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  linked_campaign_id:
                    type: string
                    description: |
                      The campaign this audience is currently linked to.
    delete:
      summary: Unlink an audience from a campaign
      operationId: unlinkAudienceFromCampaign
      description: >
        Removes the audience from the campaign's audience list. The audience
        itself is **not** deleted and can be linked to a different campaign or
        used independently afterward. Idempotent.
              Requires 'purchase' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Campaign ID.
        - name: audience_id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience ID to unlink.
      responses:
        "204":
          description: |
            Audience unlinked.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/operations:
    get:
      summary: Get the org's operational event feed (Ops Rail)
      operationId: getOperations
      description: >
        Returns operational events (segment builds, refreshes, deliveries, and
        micro-batch matches) as a persisted, append-only log. A single async
        operation (e.g. a matched-segment build) can appear as multiple events
        over time (`started`, `completed` or `failed`), each with its own
        timestamp and status.


        **Scope:** Defaults to every event across the org (`scope=org`). Pass
        `scope=user` to see only events initiated by the caller. Optionally
        narrow further with `event_type` and `campaign_id`. "org" always means
        whichever org the caller is currently acting as — for a Parent-Org/
        agency member this is the child org they've toggled to
        (`x-target-org-id`), not a fixed home org. Returns 400 `NO_ORG_CONTEXT`
        if the caller has no org context at all (an agency member who hasn't
        toggled to a child org yet).


        **Window:** Defaults to the last 3 hours (`since` omitted). Pass an
        explicit `since`/`before` window to look further back — this log is
        retained for 2 years (it also backs the org's Admin Audit Log view).


        Requires 'discovery' scope.
      tags:
        - Campaigns
      security:
        - bearerAuth: []
      parameters:
        - name: scope
          in: query
          required: false
          schema:
            type: string
            enum:
              - org
              - user
            default: org
          description: >
            `org` returns events from every member of the caller's org; `user`
            restricts results to events initiated by the caller.
        - name: since
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: >
            Only return events at or after this timestamp. Defaults to 3 hours
            before the request time.
        - name: before
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: >
            Only return events strictly before this timestamp — combine with the
            oldest `ts` from a prior page to page further back in time.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 200
            maximum: 500
        - name: event_type
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/OperationEventType"
        - name: campaign_id
          in: query
          required: false
          schema:
            type: string
          description: >
            Restrict to events associated with this campaign (most segment,
            audience, and micro-batch-match activity has no campaign association
            at all — this filter only matches events that do).
      responses:
        "200":
          description: |
            Operational events, most recent first.
          content:
            application/json:
              schema:
                type: object
                required:
                  - events
                  - total
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/OperationEvent"
                  total:
                    type: integer
              examples:
                mixed_events:
                  summary: A completed delivery and an in-progress segment build
                  value:
                    events:
                      - event_id: evt_abc
                        org_id: org_789
                        type: delivery.completed
                        status: completed
                        is_terminal: true
                        operation_key: delivery:del_abc
                        actor_uid: user_123
                        campaign_id: null
                        subject:
                          kind: delivery
                          id: del_abc
                          name: null
                        subtype: null
                        category: operational
                        payload:
                          destination: download
                          record_count: 47821
                        ts: 2026-05-30T18:08:43Z
                      - event_id: evt_def
                        org_id: org_789
                        type: segment.build.started
                        status: pending
                        is_terminal: false
                        operation_key: segment:seg_456
                        actor_uid: user_123
                        campaign_id: null
                        subject:
                          kind: segment
                          id: seg_456
                          name: Lookalike v2
                        subtype: similarity
                        category: operational
                        payload: {}
                        ts: 2026-05-30T18:10:00Z
                    total: 2
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/segments:
    post:
      summary: Create a segment
      operationId: createSegment
      description: >
        Creates a typed cohort definition (segment). The `subtype` attribute
        controls which additional parameters are required and what the platform
        does next.


        **Subtypes:**


        - **filter** (default) — Saves a named filter expression. Returns
        `status: active`
          immediately. Use `filters`/`filter_groups` for the criteria.
          Pass `create_audience: true` to also create a thin audience wrapper
          referencing this segment. Default: `false` — the segment is saved for
          later composition into an audience once iterative refinement is complete.


        - **matched** — Upload your own customer list for identity resolution.
          Returns `status: pending` + `upload_url` (30-minute signed upload URL) —
          or `upload_urls` (an array, one per shard) when `shard_count` > 1,
          in which case `upload_url` is absent. Input may be raw or gzip.
          With `hitl` unset/false (default): upload your file(s) and the platform
          runs column mapping and identity resolution automatically once every
          declared shard has been uploaded, transitioning
          to `status: active` when complete — no further calls required. With
          `hitl: true`: the upload is staged instead of resolving automatically —
          see the `hitl` field description below for the full preview/confirm/cancel
          sequence. When `create_audience: true` (default for matched), a thin
          audience wrapper is also created automatically.


        - **similarity** — Lookalike modelling. Provide `seed_description`
        (natural language
          ICP persona) or `seed_segment_id` (an existing segment) — not both — to launch a
          new workflow. Returns `status: pending` + `workflow_run_id`.
          Alternatively, provide `generation_metadata_id` (an existing `icp_id`) with no seed
          fields to attach that already-completed ICP run directly — no workflow is launched,
          `status: active` immediately. Default `create_audience: false`.


        - **propensity** — ML propensity scoring. Provide
        `positive_class_segment_id`
          (an existing segment whose members are the positive training class) to launch a
          new workflow. Returns `status: pending` + `workflow_run_id`.
          Alternatively, provide `generation_metadata_id` (an existing `model_id`) with no
          `positive_class_segment_id` to attach that already-completed model run directly —
          no workflow is launched, `status: active` immediately. Default `create_audience: false`.


        Poll `GET /v1/segments/{id}` for status, or configure a webhook to
        receive `segment.ready` / `segment.failed` events.
              Requires 'purchase' scope.

        For `filter` subtype: `filters`/`filter_groups` are validated against
        the live Field Catalog before the segment is created — an unknown field,
        a disallowed operator for that field, or an invalid enum value returns
        400 `INVALID_FILTER`. If the Field Catalog itself is temporarily
        unreachable, returns 502 `FILTER_CATALOG_UNAVAILABLE` rather than
        creating an unvalidated segment.
      tags:
        - Segments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: |
                    Display name for the segment.
                subtype:
                  type: string
                  enum:
                    - filter
                    - matched
                    - similarity
                    - propensity
                  default: filter
                  description: >
                    Segment subtype. Determines which additional attributes are
                    required.
                create_audience:
                  type: boolean
                  description: >
                    When true, automatically creates a thin audience wrapper
                    referencing this segment. Defaults: filter=true,
                    matched=true, similarity=false, propensity=false.
                filters:
                  type: array
                  items:
                    $ref: "#/components/schemas/Filter"
                  description: filter subtype only. Flat filter array (legacy). Prefer
                    filter_groups.
                filter_groups:
                  type: array
                  items:
                    $ref: "#/components/schemas/FilterGroup"
                  description: filter subtype only. Grouped filter conditions.
                file_format:
                  type: string
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  default: csv
                  description: >
                    matched subtype only. Input file format. csv (default)
                    treats every column as a single scalar value —
                    multi-value/array attributes are not supported in csv. avro,
                    json, and jsonl support native array columns for attributes
                    that require them — json and jsonl are both parsed as
                    newline-delimited JSON (one record object per line), not a
                    single top-level JSON array.
                compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    matched subtype only. gzip means the complete underlying
                    format file is a gzip stream. Upload raw gzip bytes using
                    the underlying format's Content-Type and no
                    Content-Encoding. gzip is not supported with
                    file_format=avro (avro is already internally compressed) —
                    rejected with 400 UNSUPPORTED_INPUT_COMPRESSION.
                shard_count:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 1
                  description: >
                    matched subtype only. Set > 1 for same-format shards. Every
                    CSV shard must include the same header row, and all shards
                    must share schema and compression. Returns upload_urls
                    instead of upload_url.
                session_id:
                  type: string
                  description: >
                    Optional chat session identifier used to reconnect progress
                    for an asynchronously generated similarity or propensity
                    segment.
                seed_description:
                  type: string
                  description: >
                    similarity subtype only. Natural language description of the
                    target ICP persona. Provide either seed_description or
                    seed_segment_id, not both.
                seed_segment_id:
                  type: string
                  description: >
                    similarity subtype only. ID of an existing segment to use as
                    the lookalike seed. Provide either seed_description or
                    seed_segment_id, not both.
                positive_class_segment_id:
                  type: string
                  description: >
                    propensity subtype only. ID of an existing segment whose
                    members represent the positive training class for ML model
                    training.
                model_type:
                  type: string
                  enum:
                    - BOOSTED_TREE_CLASSIFIER
                    - AUTOML_CLASSIFIER
                    - LOGISTIC_REG
                  default: BOOSTED_TREE_CLASSIFIER
                  description: propensity subtype only. BQML classifier type.
                excluded_columns:
                  type: string
                  description: propensity subtype only. Comma-prefixed feature columns excluded to
                    prevent target leakage.
                model_name:
                  type: string
                  description: propensity subtype only. Human-readable model name.
                generation_metadata_id:
                  type: string
                  description: >
                    similarity subtype: an existing icp_id. propensity subtype:
                    an existing model_id. When provided without
                    seed_description/seed_segment_id (similarity) or
                    positive_class_segment_id (propensity), attaches that
                    already-completed run to a new segment instead of launching
                    a new workflow — status: active immediately, no
                    workflow_run_id.
                visibility:
                  type: string
                  enum:
                    - org
                    - private
                  description: >
                    org (default) — visible to all org members. private —
                    visible only to creator.
                hitl:
                  type: boolean
                  default: false
                  description: >
                    Requires an explicit confirmation step before the automated
                    pipeline proceeds. For similarity/propensity, the workflow
                    pauses at a human-in-the-loop review gate. For matched, the
                    uploaded file is staged instead of landing at the path that
                    triggers automatic identity resolution — call `POST
                    /v1/match/{id}/analyze` to preview column mappings against
                    the staged file, then `POST /v1/segments/{id}/mappings` to
                    confirm (moves the file into place and starts resolution) or
                    `POST /v1/segments/{id}/mappings/cancel` to abort (the
                    segment is left completely untouched). Defaults to false —
                    omit for the existing automatic-resolution behavior.
                campaign_id:
                  type: string
                  description: >
                    Optional. If provided and create_audience is true, the
                    created audience is automatically linked to this campaign.
                webhook_url:
                  type: string
                  format: uri
                  description: >
                    Optional HTTPS URL to receive segment.ready or
                    segment.failed events. Overrides the org-level webhook URL
                    for this request only.
                ephemeral:
                  type: boolean
                  default: false
                  description: >
                    filter subtype only. When true, creates a
                    scratch/free/auto-expiring segment with a short,
                    hour-granular TTL instead of the standard 90-day expiry.
                    Excluded from `GET /v1/segments` by default (see
                    `include_ephemeral` there). Promote it to persistent later
                    via `PATCH /v1/segments/{id} {ephemeral: false}`, or
                    implicitly by composing it into an audience. Defaults to
                    `false` (persistent, today's behavior).
            examples:
              filter:
                summary: Filter segment
                value:
                  name: West Coast Adults 25-44
                  subtype: filter
                  filter_groups:
                    - id: g1
                      filters:
                        - field: state
                          op: IN
                          value:
                            - CA
                            - OR
                            - WA
                      combinator: AND
                  create_audience: true
              matched:
                summary: Matched segment (customer CSV upload)
                value:
                  name: Q3 CRM Upload
                  subtype: matched
                  file_format: csv
                  create_audience: true
              similarity:
                summary: Similarity segment (lookalike)
                value:
                  name: Lookalike of best customers
                  subtype: similarity
                  seed_description: Homeowners aged 35-55 with household income above $80k
                  hitl: false
              propensity:
                summary: Propensity segment (ML scoring)
                value:
                  name: High-Intent Refinance Prospects
                  subtype: propensity
                  positive_class_segment_id: seg_abc123
      responses:
        "201":
          description: |
            Segment created.
          content:
            application/json:
              schema:
                type: object
                required:
                  - segment_id
                  - name
                  - subtype
                  - status
                  - record_count
                  - expires_at
                  - ephemeral
                  - created_at
                  - updated_at
                properties:
                  segment_id:
                    type: string
                  name:
                    type: string
                  subtype:
                    type: string
                    enum:
                      - filter
                      - matched
                      - similarity
                      - propensity
                  status:
                    type: string
                    enum:
                      - pending
                      - active
                      - failed
                      - archived
                      - expired
                  record_count:
                    type:
                      - integer
                      - "null"
                  expires_at:
                    type: string
                    description: >
                      Date-only ISO string (90-day expiry) for a persistent
                      segment; a full ISO datetime (short, hour-granular TTL)
                      while `ephemeral` is true.
                  ephemeral:
                    type: boolean
                    description: >
                      True for a scratch/auto-expiring filter-subtype segment.
                      Absent/false reads as persistent.
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
                  upload_url:
                    type:
                      - string
                      - "null"
                    description: >
                      matched subtype only. 30-minute signed upload URL. Present
                      when shard_count was 1 (the default) — absent when
                      sharded, use upload_urls instead.
                  upload_urls:
                    type:
                      - array
                      - "null"
                    items:
                      type: string
                    description: >
                      matched subtype only. N signed upload URLs
                      (index-ordered). Present only when shard_count > 1 was
                      requested — upload_url is absent. Every CSV shard must
                      include the same header row.
                  compression:
                    type:
                      - string
                      - "null"
                    enum:
                      - none
                      - gzip
                      - null
                    description: matched subtype only. Input artifact compression.
                  upload_expires_at:
                    type:
                      - string
                      - "null"
                    format: date-time
                    description: matched subtype only. Expiry of upload_url/upload_urls.
                  workflow_run_id:
                    type:
                      - string
                      - "null"
                    description: similarity/propensity subtypes. Workflow run ID for progress
                      monitoring.
                  audience_id:
                    type:
                      - string
                      - "null"
                    description: >
                      If create_audience was true, the ID of the auto-created
                      audience wrapper. Null otherwise.
              examples:
                filter_response:
                  summary: Filter segment created
                  value:
                    segment_id: seg_abc123
                    name: West Coast Adults 25-44
                    subtype: filter
                    status: active
                    record_count: null
                    expires_at: 2026-09-27
                    ephemeral: false
                    audience_id: aud_abc123
                    created_at: 2026-06-22T18:00:00Z
                    updated_at: 2026-06-22T18:00:00Z
                matched_response:
                  summary: Matched segment created — ready for file upload
                  value:
                    segment_id: seg_m789
                    name: Q3 CRM Upload
                    subtype: matched
                    status: pending
                    record_count: null
                    expires_at: 2026-09-27
                    ephemeral: false
                    upload_url: https://storage.googleapis.com/cf-uploads/enrichment-uploads/org_x/seg_m789/input.csv?X-Goog-Signature=...
                    upload_expires_at: 2026-06-22T19:30:00.000Z
                    audience_id: aud_m789
                    created_at: 2026-06-22T18:00:00Z
                    updated_at: 2026-06-22T18:00:00Z
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "502":
          description: Field Catalog temporarily unreachable — filters could not be
            validated (code FILTER_CATALOG_UNAVAILABLE).
    get:
      summary: List segments for the caller's org
      operationId: listSegments
      description: >
        Returns all segments for the org, newest first. Supports filtering by
        subtype and status. Archived and expired segments are excluded by
        default unless explicitly requested. Listing emits no billable usage.
              Requires 'discovery' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: subtype
          in: query
          schema:
            type: string
            enum:
              - filter
              - matched
              - similarity
              - propensity
          description: |
            Filter to a specific segment subtype.
        - name: status
          in: query
          schema:
            type: string
            enum:
              - pending
              - active
              - failed
              - archived
              - expired
          description: >
            Filter by lifecycle status. archived/expired segments are excluded
            by default.
        - name: include_archived
          in: query
          schema:
            type: boolean
            default: false
          description: When true, archived segments are included in results.
        - name: include_ephemeral
          in: query
          schema:
            type: boolean
            default: false
          description: >
            When true, ephemeral (scratch/auto-expiring) segments are included
            in results. Excluded by default.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Maximum number of results to return (max 100).
        - name: cursor
          in: query
          schema:
            type: string
          description: Cursor from a previous response's `next_cursor` attribute to fetch
            the next page.
      responses:
        "200":
          description: |
            Segment list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  segments:
                    type: array
                    items:
                      $ref: "#/components/schemas/SegmentObject"
                  total:
                    type: integer
                  next_cursor:
                    type:
                      - string
                      - "null"
                    description: Pass as `cursor` on the next request. Null when no further pages
                      exist.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/segments/{id}:
    get:
      summary: Get a segment by ID
      operationId: getSegment
      description: >
        Returns the full segment document including subtype-specific attributes,
        current record count, and lifecycle status. Also includes a `usage`
        object with `audience_count` and `campaign_count` showing how many
        audiences and campaigns currently reference this segment. Reading the
        segment emits no billable usage.
              Requires 'discovery' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: |
            Segment document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SegmentObject"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      summary: Partially update a segment
      operationId: patchSegment
      description: >
        Applies a partial update to a segment. Only provided attributes are
        updated.


        **Allowed updates by subtype:**


        - **filter** — `name`, `filters`, `filter_groups`, `status`
        (archived/active), `ephemeral`

        - **matched** — `name`, `column_mappings`, `status` (archived/active),
        `ephemeral` promotion

        - **similarity / propensity** — `name`, `status` (archived/active)


        **Standalone promotion:** `ephemeral: false` promotes a scratch
        (ephemeral) segment to persistent in-place — resets its expiry to the
        standard 90-day TTL and reactivates it if it had lazily expired. Only
        `false` is honored; `true` (demoting a persistent segment back to
        scratch) is not supported and is silently ignored.


        **Count invalidation:** updating filter criteria does NOT automatically
        recompute record counts on audiences referencing this segment. Call
        `POST /v1/segments/{id}/count` to recompute.


        The update itself emits no billable usage.
              Requires 'purchase' scope.

        When `filters`/`filter_groups` are provided, they're validated against
        the live Field Catalog before the update is applied — an unknown field,
        a disallowed operator, or an invalid enum value returns 400
        `INVALID_FILTER`. If the Field Catalog is temporarily unreachable,
        returns 502 `FILTER_CATALOG_UNAVAILABLE` rather than applying an
        unvalidated update.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Rename the segment.
                status:
                  type: string
                  enum:
                    - archived
                    - active
                  description: >
                    Valid user-settable transitions: archived or active (restore
                    from archived). System-managed statuses (pending, failed,
                    expired) cannot be set via this endpoint.


                    Archiving is always safe and reversible: it never affects
                    audiences that already reference this segment (see the
                    `SegmentObject` `status` attribute docs for why). Archived
                    segments are excluded from the default `GET /v1/segments`
                    list (pass `?include_archived=true` to include them) and
                    from new audience compositions, but remain fully readable
                    via `GET /v1/segments/{id}` and can be restored with
                    `status: active` at any time.
                filters:
                  type: array
                  items:
                    $ref: "#/components/schemas/Filter"
                  description: filter subtype only. Replace the filter array.
                filter_groups:
                  type: array
                  items:
                    $ref: "#/components/schemas/FilterGroup"
                  description: filter subtype only. Replace filter groups.
                column_mappings:
                  type: object
                  additionalProperties:
                    type: string
                  description: matched subtype only. Update column-to-identity-attribute mappings.
                ephemeral:
                  type: boolean
                  description: >
                    Standalone "save this scratch segment" promotion for any
                    ephemeral segment subtype — see the endpoint description
                    above. Only `false` is honored.
      responses:
        "200":
          description: |
            Updated segment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SegmentObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/BadGateway"
    delete:
      summary: Permanently remove a segment
      operationId: deleteSegment
      description: >
        Removes the segment from every read surface: `GET /v1/segments/{id}`
        returns 404 afterward, and `GET /v1/segments` always excludes it (there
        is no override, unlike archived segments). There is no restore endpoint
        — this is a one-way operation from the API's perspective, even though
        the underlying record is retained internally rather than hard-deleted.


        **Effect on audiences:** unlike archiving, deleting a segment that's
        referenced by one or more audiences changes those audiences' composition
        — the segment is dropped from their resolved segment list and record
        count. This is the reason for the impact check below; archiving has no
        such check because it never has this effect.


        **Impact check:** if the segment is referenced by one or more active
        audiences, the server returns a 409 with `audience_count`,
        `campaign_count`, and an `audiences` list. Re-submit with
        `?confirm_impact=true` to proceed anyway.


        Not idempotent: calling this twice returns 404 on the second call, since
        the segment is already excluded from lookup after the first delete. No
        credits are debited.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: confirm_impact
          in: query
          required: false
          schema:
            type: boolean
          description: >
            Pass `true` to delete a segment that is referenced by active
            audiences. Omitting this parameter on an in-use segment returns a
            409 with impact details.
      responses:
        "204":
          description: |
            Segment successfully deleted (archived).
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: >
            Segment is referenced by one or more active audiences. Response body
            includes impact details. Re-submit with `?confirm_impact=true` to
            delete anyway.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                  - audience_count
                  - campaign_count
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - SEGMENT_IN_USE
                  message:
                    type: string
                  audience_count:
                    type: integer
                  campaign_count:
                    type: integer
                  audiences:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        linked_campaign_id:
                          type:
                            - string
                            - "null"
  /v1/segments/{id}/count:
    post:
      summary: Recount a segment
      operationId: countSegment
      description: >
        Triggers a record count computation for the segment. Runs a fresh
        computed count for every subtype: filter segments count directly against
        their own filter definition and update `record_count`;
        matched/similarity/propensity segments run the same composed count used
        by audience/delivery counting (a 1-membership composition), rather than
        echoing a possibly-stale stored count. Debits compute credits for the
        query.
              Requires 'discovery' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: |
            Count result.
          content:
            application/json:
              schema:
                type: object
                required:
                  - segment_id
                  - record_count
                properties:
                  segment_id:
                    type: string
                  record_count:
                    type:
                      - integer
                      - "null"
                    description: Updated record count. Null if the segment is pending or failed.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/segments/{id}/demographics:
    get:
      summary: Get demographic breakdown for a segment
      operationId: getSegmentDemographics
      description: >
        Returns a computed demographic breakdown for the segment. This is the
        segment-level primary path: the breakdown is evaluated directly over the
        segment's own filter definition — no audience composition (set_logic or
        exclusions) is applied. For a composed view, use GET
        /v1/audiences/{id}/demographics on a referencing audience. Works for
        every subtype: matched, similarity, and propensity segments bridge
        through a materialized scratch id-set (a 1-membership composition) so
        the same catalog-driven breakdown logic can run against them. Response
        is cached server-side for 20 minutes. Requires 'discovery' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Segment document ID.
      responses:
        "200":
          description: |
            Demographic breakdown.
          content:
            application/json:
              schema:
                type: object
                required:
                  - segment_id
                  - segment_name
                  - total_count
                  - age_breakdown
                  - geo_breakdown
                  - generated_at
                properties:
                  segment_id:
                    type: string
                  segment_name:
                    type: string
                  total_count:
                    type: integer
                    description: Total records matching the segment's filter definition.
                  age_breakdown:
                    type: array
                    items:
                      type: object
                      required:
                        - label
                        - pct
                      properties:
                        label:
                          type: string
                          description: Age bucket label.
                        pct:
                          type: number
                          description: Percentage of total_count in this bucket (0-100, rounded).
                  geo_breakdown:
                    type: array
                    items:
                      type: object
                      required:
                        - label
                        - count
                      properties:
                        label:
                          type: string
                          description: Geography value (e.g. state).
                        count:
                          type: integer
                  breakdown_extra:
                    type: array
                    description: >
                      Present only when the query returns a behavioral or
                      financial breakdown alongside age/geo.
                    items:
                      type: object
                      required:
                        - label
                        - pct
                      properties:
                        label:
                          type: string
                        pct:
                          type: number
                  extra_field:
                    type: string
                    description: >
                      Name of the attribute behind breakdown_extra. Present only
                      alongside breakdown_extra.
                  generated_at:
                    type: string
                    format: date-time
              example:
                segment_id: seg_abc123
                segment_name: Sun Belt homeowners
                total_count: 182400
                age_breakdown:
                  - label: 25-34
                    pct: 22
                  - label: 35-44
                    pct: 31
                geo_breakdown:
                  - label: AZ
                    count: 61200
                  - label: TX
                    count: 121200
                generated_at: 2026-07-05T18:04:11.000Z
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/segments/{id}/audiences:
    get:
      summary: List audiences referencing a segment
      operationId: getSegmentAudiences
      description: >
        Returns all non-deleted audiences that include this segment, with
        campaign linkage — enables cross-campaign segment usage navigation.
        Requires 'discovery' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Segment ID.
      responses:
        "200":
          description: Audiences referencing this segment.
          content:
            application/json:
              schema:
                type: object
                required:
                  - audiences
                  - audience_count
                  - campaign_count
                properties:
                  audiences:
                    type: array
                    items:
                      type: object
                      required:
                        - id
                        - name
                        - linked_campaign_id
                        - status
                        - record_count
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        linked_campaign_id:
                          type:
                            - string
                            - "null"
                        status:
                          type: string
                          enum:
                            - active
                            - expired
                            - pending
                            - failed
                            - archived
                        record_count:
                          type:
                            - integer
                            - "null"
                  audience_count:
                    type: integer
                  campaign_count:
                    type: integer
              example:
                audiences:
                  - id: aud_abc123
                    name: Sun Belt SMB Q2
                    linked_campaign_id: cmp_xyz789
                    status: active
                    record_count: 12000
                audience_count: 1
                campaign_count: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/segments/{id}/duplicate:
    post:
      summary: Duplicate a segment with lineage tracking
      operationId: duplicateSegment
      description: >
        Creates a new segment pre-populated from the source segment. Core and
        subtype-specific attributes are carried; underlying computed data (match
        results, lookalike/propensity scores) is referenced, never recomputed.
        Records parent_segment_id, derived_from (root ancestor — propagates
        through duplicate-of-duplicate chains), and parent_segment_version for
        lineage. The duplicate gets fresh created_at/updated_at and a fresh
        90-day expires_at. No audience wrapper is created.


        **Per-subtype behavior:**


        - **filter** — status active, record_count reset to null (recount via
          `POST /v1/segments/{id}/count`), version history restarts at
          `current_version: 1`.


        - **matched** — record_count and match_count carried. The duplicate
          references the source's stored match results, so it stays deliverable
          without re-running the match. Sources with expired/failed status or incomplete match data
          are rejected with 409 SOURCE_MATCH_UNAVAILABLE.


        - **similarity/propensity** — seed/model references and record_count
          carried; the duplicate points at the same generated scores.


        Pending or failed sources are rejected with 409 SOURCE_NOT_READY.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Source segment document ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: >
                    Display name for the duplicate. Defaults to "<source name>
                    (copy)".
      responses:
        "201":
          description: |
            Duplicated segment created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SegmentObject"
              example:
                segment_id: seg_dup456
                name: West Coast Adults 25-44 (copy)
                subtype: filter
                status: active
                record_count: null
                expires_at: 2026-10-03
                ephemeral: false
                visibility: org
                current_version: 1
                filters: []
                filter_groups:
                  - id: g1
                    filters:
                      - field: state
                        op: IN
                        value:
                          - CA
                          - OR
                          - WA
                    combinator: AND
                parent_segment_id: seg_abc123
                derived_from: seg_abc123
                parent_segment_version: 3
                created_at: 2026-07-05T18:00:00Z
                updated_at: 2026-07-05T18:00:00Z
                integration_provenance: null
                integration_collection_provenance: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: >
            The source segment cannot be duplicated. SOURCE_MATCH_UNAVAILABLE —
            matched source whose status is pending/expired/failed or whose
            matching_status is incomplete (its match data cannot be referenced).
            SOURCE_NOT_READY — non-matched source with status pending or failed.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - SOURCE_MATCH_UNAVAILABLE
                      - SOURCE_NOT_READY
                  message:
                    type: string
              example:
                error: Conflict
                code: SOURCE_MATCH_UNAVAILABLE
                message: The source segment's match data is unavailable (status 'expired'). Only
                  matched segments with completed match data can be duplicated.
  /v1/segments/bulk:
    patch:
      summary: Bulk update segments
      operationId: bulkPatchSegments
      description: >
        Apply the same name and/or status change to up to 50 segments in one
        request. Only name and status are bulk-patchable — they are valid across
        all four subtypes. Composition and subtype-specific attributes (filters,
        filter_groups, column_mappings) are rejected with 400 INVALID_FIELD; use
        `PATCH /v1/segments/{id}` for those. Best-effort per segment: each
        segment is updated independently and failures (not found, invalid status
        transition) are reported per id in the `failed` array — successful
        updates are never rolled back. The single-PATCH status transition rule
        applies per segment: only archived segments can be restored to active.
        Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - segment_ids
              properties:
                segment_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
                  description: IDs of segments to update (1-50).
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: New display name applied to every listed segment.
                status:
                  type: string
                  enum:
                    - active
                    - archived
                  description: >
                    New status applied to every listed segment. Only
                    user-settable values are accepted; 'active' is only valid on
                    segments currently archived (per-segment failure otherwise).
      responses:
        "200":
          description: >
            Bulk update processed. Per-segment failures, if any, are listed in
            `failed`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - updated
                  - failed
                properties:
                  updated:
                    type: integer
                    description: Number of segments successfully updated.
                  failed:
                    type: array
                    description: Segments that could not be updated.
                    items:
                      type: object
                      required:
                        - id
                        - error
                      properties:
                        id:
                          type: string
                        error:
                          type: string
              example:
                updated: 2
                failed:
                  - id: seg_missing
                    error: Not found
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    delete:
      summary: Bulk archive segments
      operationId: bulkArchiveSegments
      description: >
        Archive up to 50 segments in one request by setting `status: archived`.
        Deliberately DIVERGES from `DELETE /v1/segments/{id}`: the
        single-segment DELETE permanently removes the segment (sets `deleted:
        true` behind a SEGMENT_IN_USE impact gate, dropping it out of every
        referencing audience's composition), while bulk archive only changes
        status — archived segments remain fully resolvable by audiences that
        reference them, so composition integrity is preserved and no impact gate
        is needed. Fully reversible via `PATCH {status: active}` (single or
        bulk). Best-effort per segment: not-found/deleted ids are reported in
        the `failed` array; already-archived segments count as success
        (idempotent). Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - segment_ids
              properties:
                segment_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
                  description: IDs of segments to archive (1-50).
      responses:
        "200":
          description: >
            Bulk archive processed. Per-segment failures, if any, are listed in
            `failed`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - archived
                  - failed
                properties:
                  archived:
                    type: integer
                    description: >
                      Number of segments now archived (includes already-archived
                      segments, which are idempotent successes).
                  failed:
                    type: array
                    description: Segments that could not be archived.
                    items:
                      type: object
                      required:
                        - id
                        - error
                      properties:
                        id:
                          type: string
                        error:
                          type: string
              example:
                archived: 3
                failed:
                  - id: seg_missing
                    error: Not found
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/segments/bulk-delete:
    delete:
      summary: Bulk hard-delete segments
      operationId: bulkDeleteSegments
      description: >
        Permanently delete up to 50 segments in one request — the bulk
        counterpart of `DELETE /v1/segments/{id}` (sets `deleted: true`,
        dropping each segment out of every referencing audience's composition).
        Unlike the single-segment DELETE, there is no `confirm_impact` bypass
        here: a segment referenced by 1+ audiences is always skipped (never
        force-deleted) and reported back in `skipped`, since a bulk action has
        no per-segment confirmation UI to make an informed override decision.
        Delete the segment individually via `DELETE
        /v1/segments/{id}?confirm_impact=true` to force it. Best-effort per
        segment: not-found/already-deleted ids are reported in `failed`.
        Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - segment_ids
              properties:
                segment_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
                  description: IDs of segments to delete (1-50).
      responses:
        "200":
          description: >
            Bulk delete processed. In-use segments and per-segment failures, if
            any, are listed in `skipped`/`failed` respectively.
          content:
            application/json:
              schema:
                type: object
                required:
                  - deleted
                  - skipped
                  - failed
                properties:
                  deleted:
                    type: integer
                    description: Number of segments permanently deleted.
                  skipped:
                    type: array
                    description: Segments left untouched because they're referenced by 1+ audiences.
                    items:
                      type: object
                      required:
                        - id
                        - audience_count
                        - campaign_count
                      properties:
                        id:
                          type: string
                        audience_count:
                          type: integer
                        campaign_count:
                          type: integer
                  failed:
                    type: array
                    description: Segments that could not be deleted (not found/already deleted).
                    items:
                      type: object
                      required:
                        - id
                        - error
                      properties:
                        id:
                          type: string
                        error:
                          type: string
              example:
                deleted: 1
                skipped:
                  - id: seg_in_use
                    audience_count: 2
                    campaign_count: 1
                failed:
                  - id: seg_missing
                    error: Not found
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/segments/{id}/refresh:
    post:
      summary: Refresh a segment against the latest data
      operationId: refreshSegment
      description: >
        Re-derives a single segment from the latest identity graph data. This is
        the segment-native counterpart of `POST /v1/audiences/{id}/refresh` — it
        updates ONLY this segment. Referencing audiences are never touched;
        their IDs are returned in `affected_audiences` so you can recount or
        refresh them as needed (use the audience-level refresh for a
        whole-composition refresh).


        **Per-subtype behavior:**


        - **filter** — Synchronous. Bumps `current_version` and appends a
          version history entry (sharing the same counter as PATCH filter
          edits), clears `record_count`, resets the 90-day TTL, and returns
          `status: active`. If nothing would change (already active, count
          already null, TTL already fresh today) and `force` is not set, the
          call is a no-op returning the current segment with
          `refreshed: false`.


        - **matched** — Returns a fresh presigned `upload_url` (24 h expiry) —
        or
          `upload_urls` (an array, one per shard) when `shard_count` > 1,
          in which case `upload_url` is absent. The new upload may be raw or
          gzip. With `hitl`
          unset/false (default): targets the segment's existing upload path
          directly, resets mapping state (`column_mappings`,
          `mapping_confirmed`) and counts, and returns `status: pending` —
          re-upload your identity list and identity resolution runs
          automatically once every declared shard has been uploaded. With
          `hitl: true`: the URL(s) instead target a staging location and the
          segment's status/mapping state is left untouched until you confirm
          — see the `hitl` field description below. Matched duplicates that
          share another segment's data are rejected with 409
          SHARED_SOURCE_DATA — refresh the source segment instead. A saved
          integration-match segment owns its own lifecycle and remains refreshable.


        - **similarity** — Re-launches the lookalike generation workflow with
          the stored seed parameters. Returns `status: pending`; the segment's
          `workflow_run_id` is updated to the new run. Poll
          `GET /v1/workflows/{workflow_run_id}` until completion.


        - **propensity** — Re-launches the scoring model workflow using the
          stored `positive_class_segment_id`. Returns `status: pending` plus
          the new `workflow_run_id`. 422 MISSING_POSITIVE_CLASS_ID for legacy
          segments that predate `positive_class_segment_id` storage — those
          must be recreated.


        Archived segments are rejected with 409 SEGMENT_ARCHIVED — restore first
        via `PATCH /v1/segments/{id}` with `status: active`.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Segment document ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                hitl:
                  type: boolean
                  default: false
                  description: >
                    Requires an explicit confirmation step before the automated
                    pipeline proceeds. **(similarity / propensity)** the
                    re-launched workflow pauses at a human-in-the-loop review
                    gate — use `POST /v1/workflows/{workflow_run_id}/resume` to
                    submit answers and resume. **(matched)** the re-upload is
                    staged instead of landing at the path that triggers
                    automatic identity resolution — call `POST
                    /v1/match/{id}/analyze` to preview column mappings against
                    the staged file, then `POST /v1/segments/{id}/mappings` to
                    confirm (moves the file into place and starts resolution) or
                    `POST /v1/segments/{id}/mappings/cancel` to abort (the
                    segment is left completely untouched). Ignored for filter.
                force:
                  type: boolean
                  default: false
                  description: >
                    **(filter only)** Bump the version and invalidate the count
                    even when the segment already looks fresh (skips the no-op
                    short-circuit).
                shard_count:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 1
                  description: >
                    **(matched only)** Set > 1 for same-format shards. Every CSV
                    shard must include the same header, and all shards must
                    share schema and compression. Returns `upload_urls` instead
                    of `upload_url`.
                compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    **(matched only)** Compression for this new upload. It does
                    not inherit the previous upload's setting. Send raw gzip
                    bytes without Content-Encoding. gzip is not supported for
                    avro-format segments — rejected with 400
                    UNSUPPORTED_INPUT_COMPRESSION.
      responses:
        "200":
          description: >
            Segment refreshed (or no-op — inspect `refreshed`). Shape varies by
            subtype: matched adds `upload_url`/`upload_expires_at` (or
            `upload_urls` when `shard_count` > 1); similarity/propensity carry
            the new `workflow_run_id`. See `next_step` for a machine-readable
            hint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SegmentRefreshResult"
              example:
                segment_id: seg_abc123
                name: West Coast Adults 25-44
                subtype: filter
                status: active
                record_count: null
                expires_at: 2026-10-03
                ephemeral: false
                visibility: org
                current_version: 4
                filters: []
                filter_groups:
                  - id: g1
                    filters:
                      - field: state
                        op: IN
                        value:
                          - CA
                          - OR
                          - WA
                    combinator: AND
                created_at: 2026-04-01T12:00:00Z
                updated_at: 2026-07-05T18:00:00Z
                refreshed: true
                affected_audiences:
                  - aud_123
                  - aud_456
                next_step: Call POST /v1/segments/{id}/count (recount) to refresh the record
                  count. Referencing audiences are NOT auto-refreshed — recount
                  or refresh the audiences listed in affected_audiences as
                  needed.
                integration_provenance: null
                integration_collection_provenance: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: >
            The segment cannot be refreshed. SEGMENT_ARCHIVED — the segment is
            archived; restore it first (PATCH `status: active`).
            SHARED_SOURCE_DATA — matched duplicate that references another
            segment's match data; refreshing it here would overwrite the source
            segment's upload. The message names the source segment id to refresh
            instead.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - SEGMENT_ARCHIVED
                      - SHARED_SOURCE_DATA
                  message:
                    type: string
              example:
                error: Conflict
                code: SHARED_SOURCE_DATA
                message: "This matched segment is a duplicate that references segment
                  'seg_root1' for its match data — refreshing it here would
                  overwrite that source segment's upload. Refresh the source
                  segment instead: POST /v1/segments/seg_root1/refresh."
        "422":
          description: >
            MISSING_POSITIVE_CLASS_ID — **(propensity only)** the segment
            predates `positive_class_segment_id` storage and cannot be
            auto-refreshed; delete and recreate it.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - MISSING_POSITIVE_CLASS_ID
                  message:
                    type: string
  /v1/segments/{id}/mappings:
    post:
      summary: Confirm column mapping overrides for a matched segment
      operationId: confirmSegmentMappings
      description: >
        Stores confirmed column mapping overrides for a matched-subtype segment.
        Call this after reviewing the `POST /v1/match/{id}/analyze` response to
        correct any incorrect predictions. Sets `mapping_confirmed: true` so the
        platform skips its own analysis and uses these mappings directly.


        `column_mappings` may be omitted entirely — the platform then re-runs
        column analysis on the currently active upload itself and accepts its
        own suggested mapping as-is (the same behavior as reviewing `POST
        /v1/match/{id}/analyze` and confirming without edits, in one call). This
        can return 409 `FILE_NOT_YET_UPLOADED` if the file hasn't finished
        uploading yet, or 422/400 on other analysis failures — pass
        `column_mappings` explicitly to skip re-analysis entirely.


        If the segment was created or refreshed with `hitl: true`, the upload is
        still staged (not yet at the path that triggers automatic resolution) —
        this call also moves the staged file into place and starts identity
        resolution, which is why it can return 500 `STAGING_COPY_FAILED` if that
        move fails (the segment is left unchanged in that case; retry the call).
        Use `POST /v1/segments/{id}/mappings/cancel` instead to abort a staged
        upload without confirming.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Segment document ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                column_mappings:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Map of source column name → standard identity attribute
                    name. Use `"(skip)"` as the value to explicitly exclude a
                    column. Omit entirely (send `{}` or no body) to accept the
                    platform's own fresh column analysis of the active upload
                    as-is.
      responses:
        "200":
          description: Mappings saved.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                  - column_mappings
                properties:
                  ok:
                    type: boolean
                  column_mappings:
                    type: object
                    additionalProperties:
                      type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "500":
          description: >
            STAGING_COPY_FAILED — moving a staged (`hitl: true`) upload into
            place failed; the segment is unchanged, retry the call.
            ANALYSIS_FAILED — `column_mappings` was omitted and re-analysis
            failed unexpectedly.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - STAGING_COPY_FAILED
                      - ANALYSIS_FAILED
                  message:
                    type: string
  /v1/segments/{id}/mappings/cancel:
    post:
      summary: Abort a staged (hitl:true) matched-segment upload without confirming
      operationId: cancelSegmentMappings
      description: >
        Clears the staged upload created by a `hitl: true` call to `POST
        /v1/segments` or `POST /v1/segments/{id}/refresh`. Performs no file move
        and no status/mapping-state change — the segment is left exactly as it
        was before the upload was initiated. Idempotent: returns 200 even if
        nothing is currently staged.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Segment document ID.
      responses:
        "200":
          description: Staged upload cleared (or already clear).
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/segments/{id}/upload-failed:
    post:
      summary: Report that a matched segment's client-side re-upload failed
      operationId: reportSegmentUploadFailed
      description: >
        Called when a matched segment's re-upload (kicked off by `POST
        /v1/segments/{id}/refresh` or `POST /v1/audiences/{id}/refresh`) fails
        client-side before ever reaching storage — e.g. a browser-side network
        or CORS failure on the presigned upload PUT. The async pipeline that
        would normally fail the segment never starts in that case, since no
        upload ever arrived, so without this call the segment (and any linked
        audience) would otherwise stay `pending` until a much slower backstop
        eventually catches it.


        Idempotent — a no-op (`already_resolved: true`) if the segment has
        already moved past `pending` (e.g. the upload actually succeeded). Only
        valid for matched-subtype segments.
              Requires 'purchase' scope.
      tags:
        - Segments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Segment document ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                audience_id:
                  type: string
                  description: >
                    Optional linked audience to also mark failed, in addition to
                    the segment's own linked audience (if any).
                reason:
                  type: string
                  maxLength: 500
                  description: Human-readable failure reason.
      responses:
        "200":
          description: Segment (and any linked audience) marked failed, or already resolved.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                  - already_resolved
                properties:
                  ok:
                    type: boolean
                  already_resolved:
                    type: boolean
                    description: True if the segment had already moved past pending before this
                      call.
              example:
                ok: true
                already_resolved: false
        "400":
          description: NOT_A_MATCHED_UPLOAD — only matched-subtype segments have an upload
            to report as failed.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - NOT_A_MATCHED_UPLOAD
                  message:
                    type: string
              example:
                error: Bad Request
                code: NOT_A_MATCHED_UPLOAD
                message: Only matched-subtype segments have an upload to report as failed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/audiences:
    post:
      summary: Create an audience
      operationId: createAudience
      description: >
        Creates a named audience — a subtype-agnostic composition wrapper that
        references one or more segments (include or exclude role) for delivery.


        Audiences are always `status: active` immediately on creation. The
        underlying segments determine data readiness — only deliver an audience
        after all its include-role segments are active.


        **Composition options:**


        - Use `segment_refs` (preferred) to specify each segment and its role
          (`include` or `exclude`) explicitly.

        - Use `segment_ids` / `excluded_segment_ids` (convenience shorthand)
          as a flat list when all included segments share the same include role.

        - Use `set_logic` to control how multiple include-role segments are
          combined: `union` (default) — a record matches ANY included segment (OR) —
          or `intersection` — a record must match EVERY included segment (AND).

        - Excluded segments are always subtracted from the composed include
          result (AND NOT), regardless of `set_logic`:
          result = (included segments combined by set_logic) MINUS (excluded segments).


        To create matched, similarity, or propensity segments, use `POST
        /v1/segments`.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: |
                    Display name for the audience.
                segment_refs:
                  type: array
                  description: >
                    Preferred composition format. Each entry specifies a segment
                    and its role. Supersedes `segment_ids` /
                    `excluded_segment_ids` if provided.
                  items:
                    type: object
                    required:
                      - segment_id
                      - role
                    properties:
                      segment_id:
                        type: string
                      role:
                        type: string
                        enum:
                          - include
                          - exclude
                segment_ids:
                  type: array
                  items:
                    type: string
                  description: >
                    Convenience shorthand: IDs of segments to include (role:
                    include). Use segment_refs for mixed include/exclude
                    compositions.
                excluded_segment_ids:
                  type: array
                  items:
                    type: string
                  description: >
                    Convenience shorthand: IDs of segments to exclude from the
                    composition.
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                  default: union
                  description: >
                    How to combine the include-role segments. Exclusions always
                    apply as AND NOT.
                visibility:
                  type: string
                  enum:
                    - org
                    - private
                  description: >
                    org (default) — visible to all members of the org. private —
                    visible only to the creating user.
                campaign_id:
                  type: string
                  description: >
                    Optional. If provided, the created audience is automatically
                    linked to this campaign. The audience can belong to at most
                    one campaign at a time. Omit if you are not using campaign
                    workspaces.
            examples:
              segment_refs:
                summary: Audience with explicit segment roles
                value:
                  name: West Coast Adults 25-44
                  segment_refs:
                    - segment_id: seg_abc123
                      role: include
                    - segment_id: seg_def456
                      role: include
                    - segment_id: seg_sup789
                      role: exclude
                  set_logic: union
              shorthand:
                summary: Audience using shorthand segment_ids
                value:
                  name: Q3 CRM Audience
                  segment_ids:
                    - seg_abc123
                    - seg_def456
                  set_logic: union
              with_campaign:
                summary: Audience linked to a campaign
                value:
                  name: Q3 West Coast Adults
                  segment_ids:
                    - seg_abc123
                  campaign_id: camp_abc123
      responses:
        "201":
          description: |
            Audience created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceObject"
              examples:
                created:
                  summary: Audience created
                  value:
                    audience_id: aud_abc123
                    name: West Coast Adults 25-44
                    status: active
                    version: 1
                    record_count: null
                    expires_at: 2026-09-27
                    visibility: org
                    segment_refs:
                      - segment_id: seg_abc123
                        role: include
                      - segment_id: seg_def456
                        role: include
                    segment_ids:
                      - seg_abc123
                      - seg_def456
                    excluded_segment_ids: []
                    segments:
                      - id: seg_abc123
                        name: West Coast States
                        record_count: 1204331
                        subtype: filter
                        input_record_count: null
                        match_count: null
                      - id: seg_def456
                        name: Adults 25-44
                        record_count: 8443210
                        subtype: filter
                        input_record_count: null
                        match_count: null
                    excluded_segments: []
                    set_logic: union
                    created_at: 2026-06-22T18:00:00Z
                    updated_at: 2026-06-22T18:00:00Z
                    integration_provenance: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    get:
      summary: List audiences for the caller's org
      operationId: listAudiences
      description: >
        Returns all non-deleted audiences for the org, newest first. Each row
        includes expanded `segments` / `excluded_segments` summaries (id, name,
        record_count, subtype) for its constituent segments — the same expansion
        as `GET /v1/audiences/{id}`. Requires 'discovery' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - active
              - expired
              - archived
          description: >
            Filter by audience status. `archived` audiences are excluded by
            default; pass `status=archived` to retrieve them explicitly.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: |
            Maximum number of results to return.
      responses:
        "200":
          description: |
            Audience list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  audiences:
                    type: array
                    items:
                      $ref: "#/components/schemas/AudienceObject"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/audiences/{id}:
    get:
      summary: Get an audience by ID
      operationId: getAudience
      description: >
        Returns the audience with expanded segment metadata — both include-role
        (`segments`) and exclude-role (`excluded_segments`) summaries, each
        carrying the segment's `subtype`. Requires 'discovery' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: |
            Audience document with expanded segment metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceObject"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      summary: Update an audience
      operationId: updateAudience
      description: >
        Fully replaces the audience's `name`, `segment_ids`,
        `excluded_segment_ids`, and `set_logic` with the provided values.
        Audiences are subtype-agnostic composition wrappers — to change which
        segments are composed, update the segment ID lists here.


        **Composition model:** `set_logic: union` combines included segments
        with OR (match ANY); `intersection` combines them with AND (match
        EVERY). Segments in `excluded_segment_ids` are always subtracted from
        that result (AND NOT), regardless of `set_logic`.


        **Count invalidation:** changing the segment composition **clears the
        cached `record_count`**. Call `POST /v1/audiences/{id}/count` after
        updating to recompute it. The `record_count` will be `null` until
        recomputed.


        **Versioning:** the audience `version` is incremented on every
        successful PUT. Previously completed deliveries at prior versions remain
        accessible in delivery history.


        The update itself emits no billable usage.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                segment_ids:
                  type: array
                  items:
                    type: string
                  description: |
                    Full replacement list of included segment IDs.
                excluded_segment_ids:
                  type: array
                  items:
                    type: string
                  description: |
                    Full replacement list of excluded segment IDs.
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
      responses:
        "200":
          description: |
            Updated audience.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      summary: Partially update an audience
      operationId: patchAudience
      description: >
        Applies a partial update to an audience. Accepts any subset of the
        attributes below — only provided attributes are updated. Use
        `add_segment_id` / `remove_segment_id` for incremental segment edits
        (preferred over replacing the full `segment_ids` array). Use `name` to
        rename.


        **Composition model:** `set_logic: union` combines included segments
        with OR (match ANY); `intersection` combines them with AND (match
        EVERY). Excluded segments (`add_excluded_segment_id`) are always
        subtracted from that result (AND NOT), regardless of `set_logic`.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: |
                    Rename the audience.
                status:
                  type: string
                  enum:
                    - archived
                    - active
                  description: >
                    Valid user-settable transitions: `archived` — soft-deletes
                    the audience; excluded from list results by default.
                    `active` — restores a previously archived audience back to
                    active; **rejected with 400 (`INVALID_STATUS_TRANSITION`) if
                    the current status is not `archived`**. System-managed
                    statuses (`pending`, `expired`, `failed`) cannot be set via
                    this endpoint and will be rejected with 400.
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                add_segment_id:
                  type: string
                  description: |
                    Append a single segment ID to the included list.
                remove_segment_id:
                  type: string
                  description: |
                    Remove a single segment ID from the included list.
                add_excluded_segment_id:
                  type: string
                  description: |
                    Append a single segment ID to the excluded list.
                remove_excluded_segment_id:
                  type: string
                  description: |
                    Remove a single segment ID from the excluded list.
      responses:
        "200":
          description: |
            Updated audience.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      summary: Delete an audience
      operationId: deleteAudience
      description: >
        Soft-deletes the audience by marking it as archived. The audience is
        excluded from `GET /v1/audiences` list responses and cannot be used for
        new deliveries, but its delivery history remains accessible via `GET
        /v1/audiences/{id}/deliveries`.


        **In-flight deliveries** that are already processing are not cancelled —
        they will complete normally. New delivery requests against an archived
        audience are rejected with `404`.


        To restore an archived audience, use `PATCH /v1/audiences/{id}` with `{
        "status": "active" }`. Idempotent — deleting an already-archived
        audience returns `204` without error. The archive action emits no
        billable usage.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: |
            Deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/audiences/{id}/duplicate:
    post:
      summary: Duplicate an audience with lineage tracking
      operationId: duplicateAudience
      description: >
        Duplicates an audience with lineage tracking. Preserves composition
        (segment_ids, excluded_segment_ids, set_logic) and metadata. Records
        parent_audience_id, derived_from, and the source's segment version
        snapshot for lineage. Resets record_count to null, forcing a fresh
        recount. Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Source audience document ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: >
                    Display name for the duplicate. Defaults to "<source name>
                    (copy)".
                campaign_id:
                  type: string
                  description: >
                    Optional — link the duplicate into this campaign on creation.
      responses:
        "201":
          description: |
            Duplicated audience created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/audiences/{id}/count:
    post:
      summary: Recompute record count for an audience
      operationId: recountAudience
      description: >
        Recomputes the record count for a saved audience based on its current
        composition — included segments combined by `set_logic` (`union` = OR,
        `intersection` = AND) minus any `excluded_segment_ids` (AND NOT). Call
        after adding or removing segments. Updates `audience.record_count`.
              Requires 'discovery' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
      responses:
        "200":
          description: |
            Count computed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  audience_id:
                    type: string
                  record_count:
                    type: integer
                  matched_record_count:
                    type: integer
                    description: >
                      Records contributed by matched-subtype segments in this
                      audience's composition. Present only when the composition
                      includes at least one included matched segment; absent for
                      pure-filter or non-matched compositions.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/audiences/{id}/demographics:
    get:
      summary: Get demographic breakdown for an audience
      operationId: getAudienceDemographics
      description: >
        Returns a computed demographic breakdown for the audience, evaluated
        over the audience as a composition: its include-role segments combined
        per set_logic, minus any excluded segments. Works for every composition
        — pure-filter, a single non-filter subtype (matched, similarity,
        propensity), or a genuine mix — via a materialized scratch id-set bridge
        for compositions that include a non-filter segment. Response is cached
        server-side for 20 minutes.
              Requires 'discovery' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
      responses:
        "200":
          description: |
            Demographic breakdown.
          content:
            application/json:
              schema:
                type: object
                properties:
                  audience_id:
                    type: string
                  record_count:
                    type: integer
                  demographics:
                    type: object
                    properties:
                      median_age:
                        type: number
                      female_pct:
                        type: number
                      homeowner_pct:
                        type: number
                      children_pct:
                        type: number
                      college_pct:
                        type: number
                      high_income_pct:
                        type: number
                  generated_at:
                    type: string
                    format: date-time
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/QueryExecutionError"
  /v1/match/{id}/analyze:
    post:
      summary: Analyze matched segment column mapping
      operationId: analyzeMatchSegment
      description: >
        Streams a bounded schema sample from csv/json/jsonl/Avro input (raw or
        gzip), analyzes the column structure using AI inference, and returns a
        mapping of your source columns to standard identity attributes. Takes a
        **segment_id** (returned by `POST /v1/match/file`) — no audience wrapper
        required. Call this after uploading your file to preview how columns
        will be interpreted before the match runs.


        **Response attributes to watch:**

        - `columns[].confidence`: values below 0.6 indicate uncertain mappings
        that are
          worth verifying before the match runs.

        - `columns[].warning`: an attribute-level concern flagged by the
        analyzer (e.g. ambiguous
          column name or missing country codes on phone numbers).

        - `unmapped_columns`: columns that could not be mapped and will be
        excluded from
          identity resolution. If a critical identity attribute (email, phone, last_name) appears
          here, the match will have reduced accuracy or may fail entirely.

        - `recommendations`: suggestions for improving match quality.


        **If mappings are incorrect:** rename the affected columns in your file
        to match standard identity attribute names (`email`, `phone`,
        `email_sha256`, `phone_sha256`, `first_name`, `middle_name`,
        `last_name`, `name_suffix`, `address_1`, `address_2`, `city`, `state`,
        `zip`, `dob`, `iag_person_id`) and re-upload. The match workflow will
        re-analyze automatically.


        **Analysis gate:** unless the segment was created/refreshed with `hitl:
        true`, the platform always runs its own column analysis automatically as
        part of the match workflow, and by the time this endpoint can return
        real data the upload has already triggered that automatic resolution —
        calling it does not pause or gate anything in that case, it's a
        read-only preview only. If no usable identity attributes can be
        resolved, the segment transitions to `failed` and `error_message`
        describes which columns could not be mapped.


        **With `hitl: true`:** the segment's upload is staged rather than
        resolving automatically, so this endpoint reads the staged file and
        genuinely nothing has started yet — review the response, then call `POST
        /v1/segments/{id}/mappings` to confirm (starts resolution) or `POST
        /v1/segments/{id}/mappings/cancel` to abort.
              Requires 'discovery' scope.
      tags:
        - Enrichment
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The segment_id of the matched segment (from POST /v1/match/file).
      responses:
        "200":
          description: Column mapping analysis.
          content:
            application/json:
              schema:
                type: object
                required:
                  - segment_id
                  - analysis
                properties:
                  segment_id:
                    type: string
                  analysis:
                    type: object
                    required:
                      - columns
                      - unmapped_columns
                      - recommendations
                    properties:
                      columns:
                        type: array
                        items:
                          type: object
                          required:
                            - source_column
                            - mapped_to
                            - inferred_type
                            - confidence
                            - warning
                          properties:
                            source_column:
                              type: string
                            mapped_to:
                              type:
                                - string
                                - "null"
                            inferred_type:
                              type: string
                            confidence:
                              type: number
                            warning:
                              type:
                                - string
                                - "null"
                      unmapped_columns:
                        type: array
                        items:
                          type: string
                      recommendations:
                        type: array
                        items:
                          type: string
              example:
                segment_id: seg_abc123
                analysis:
                  columns:
                    - source_column: Email Address
                      mapped_to: email
                      inferred_type: STRING
                      confidence: 0.98
                      warning: null
                    - source_column: Mobile
                      mapped_to: phone
                      inferred_type: STRING
                      confidence: 0.84
                      warning: Phone numbers lack country codes — US assumed
                  unmapped_columns: []
                  recommendations:
                    - Add country codes to phone numbers for higher match rates
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "500":
          $ref: "#/components/responses/InternalServerError"
  /v1/audiences/{id}/refresh:
    post:
      summary: Refresh a member segment and this audience's composed metadata
      operationId: refreshAudience
      description: >
        Re-derives one member segment of this audience from the latest identity
        graph data, then bumps only this audience's own composed metadata
        (`snapshot_version`/`expires_at`/`status`; `record_count` is cleared and
        must be re-requested via `POST /v1/audiences/{id}/count`).

        An audience is a composition of N member segments (possibly mixed
        subtypes) — there is no "primary" segment. You must name exactly which
        member segment to refresh via the required `segment_id` body field (list
        an audience's segments via `GET /v1/audiences/{id}`). That segment's
        refresh delegates entirely to the same implementation `POST
        /v1/segments/{id}/refresh` uses, so behavior for the named segment is
        identical either way. This route never touches any OTHER segment in the
        composition, and — since segment refresh is deliberately non-cascading —
        never touches any OTHER audience that happens to reference the same
        segment.

        Behavior is driven by the refreshed segment's subtype:


        **filter** — Synchronous refresh. The audience is immediately returned
        as `status: active` with an incremented version number and a new 90-day
        `expires_at`. The `record_count` is cleared and must be re-requested via
        `POST /v1/audiences/{id}/count`.


        **matched** — Returns a fresh presigned `upload_url` (24 h expiry) — or
        `upload_urls` (an array, one per shard) when `shard_count` > 1, in which
        case `upload_url` is absent. The new upload may be raw or gzip. With
        `hitl` unset/false (default): returns `status: pending` immediately and
        the URL(s) target the canonical upload path — re-upload your identity
        list and identity resolution runs automatically once every declared
        shard has been uploaded. With `hitl: true`: the segment's status is left
        untouched and the URL(s) instead target a staging location — call `POST
        /v1/match/{segment_id}/analyze` to preview column mappings, then `POST
        /v1/audiences/{id}/mappings` to confirm (starts resolution) or `POST
        /v1/audiences/{id}/mappings/cancel` to abort.


        **similarity/propensity** — Re-launches the generation workflow. Returns
        `status: pending` plus a `workflow_run_id`; poll `GET
        /v1/workflows/{workflow_run_id}` until completion.


        **Important:** Delivery requests are rejected with 422 while constituent
        segments are still being processed.


        To refresh a single segment without touching its audience wrappers, use
        `POST /v1/segments/{id}/refresh` — it updates only that segment and
        returns `affected_audiences` for follow-up.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - segment_id
              properties:
                segment_id:
                  type: string
                  description: >
                    Required. ID of the member segment (within this audience's
                    composition) to refresh. Rejected with 400
                    `SEGMENT_ID_REQUIRED` if omitted, or 400
                    `SEGMENT_NOT_A_MEMBER` if the segment is not a member of
                    this audience. `GET /v1/audiences/{id}` to list this
                    audience's segments.
                hitl:
                  type: boolean
                  default: false
                  description: >
                    Requires an explicit confirmation step before the automated
                    pipeline proceeds. **(similarity / propensity)** the refresh
                    workflow pauses at a human-in-the-loop review gate — use
                    `POST /v1/workflows/{workflow_run_id}/resume` with `gate_id`
                    and `response` to submit answers and resume. **(matched)**
                    the re-upload is staged instead of landing at the path that
                    triggers automatic identity resolution — see the `matched`
                    bullet above. Ignored for filter. Default: `false` (fully
                    automated).
                shard_count:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 1
                  description: >
                    **(matched only)** Set > 1 for same-format shards. Every CSV
                    shard must include the same header, and all shards must
                    share schema and compression. Returns `upload_urls` instead
                    of `upload_url`.
                compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    **(matched only)** Compression for this new upload. It does
                    not inherit the previous upload. Send raw gzip bytes without
                    Content-Encoding. gzip is not supported for avro-format
                    segments — rejected with 400 UNSUPPORTED_INPUT_COMPRESSION.
      responses:
        "200":
          description: >
            Audience refreshed. Inspect `status` to determine next steps. See
            `next_step` for a machine-readable hint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceRefreshResult"
              example:
                audience_id: aud_xyz123
                segment_id: seg_abc123
                version: 2
                version_created_at: 2026-06-30T14:00:00Z
                expires_at: 2026-09-28T14:00:00Z
                status: active
                subtype: filter
                refreshed: true
                next_step: Call recount_audience to refresh the record count before quoting or
                  delivering.
        "400":
          description: >
            `SEGMENT_ID_REQUIRED` — `segment_id` was omitted from the request
            body. `SEGMENT_NOT_A_MEMBER` — the given `segment_id` is not a
            member of this audience. `UNKNOWN_SUBTYPE` — the segment has an
            unrecognized subtype and cannot be refreshed.
            `UNSUPPORTED_INPUT_COMPRESSION` — `compression=gzip` was requested
            for an avro-format matched member segment.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - SEGMENT_ID_REQUIRED
                      - SEGMENT_NOT_A_MEMBER
                      - UNKNOWN_SUBTYPE
                      - UNSUPPORTED_INPUT_COMPRESSION
                  message:
                    type: string
              example:
                error: Bad Request
                code: SEGMENT_ID_REQUIRED
                message: segment_id is required — choose which member segment to refresh. GET
                  /v1/audiences/:id to list this audience's segments.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          description: >
            The audience does not exist (simple-tier body, no `code`), or the
            given `segment_id` does not resolve to any segment document
            (`SEGMENT_NOT_FOUND`) despite having passed the membership check
            (e.g. deleted concurrently).
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: AudienceNotFound
                    required:
                      - error
                    additionalProperties: false
                    properties:
                      error:
                        type: string
                        description: '"Audience not found"'
                  - type: object
                    title: SegmentNotFound
                    required:
                      - error
                      - code
                      - message
                    properties:
                      error:
                        type: string
                      code:
                        type: string
                        enum:
                          - SEGMENT_NOT_FOUND
                      message:
                        type: string
        "409":
          description: >
            The named segment cannot be refreshed. `SEGMENT_ARCHIVED` — the
            segment is archived; restore it first (`PATCH` `status: active` via
            `PATCH /v1/segments/{id}`). `SHARED_SOURCE_DATA` — matched duplicate
            that references another segment's match data; refreshing it here
            would overwrite the source segment's upload. The message names the
            source segment id to refresh instead.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - SEGMENT_ARCHIVED
                      - SHARED_SOURCE_DATA
                  message:
                    type: string
        "422":
          description: >
            `MISSING_POSITIVE_CLASS_ID` — **(propensity only)** the segment
            predates `positive_class_segment_id` storage and cannot be
            auto-refreshed; delete and recreate it.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - MISSING_POSITIVE_CLASS_ID
                  message:
                    type: string
        "500":
          description: >
            `WORKFLOW_LAUNCH_FAILED` — the similarity/propensity re-launch
            workflow call failed; retry. Presigned-URL generation failures for
            the matched subtype return a simple-tier body with no `code`.
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: WorkflowLaunchFailed
                    required:
                      - error
                      - code
                      - message
                    properties:
                      error:
                        type: string
                      code:
                        type: string
                        enum:
                          - WORKFLOW_LAUNCH_FAILED
                      message:
                        type: string
                  - type: object
                    title: PresignedUrlError
                    required:
                      - error
                    additionalProperties: false
                    properties:
                      error:
                        type: string
                        description: '"Failed to generate presigned upload URL"'
  /v1/audiences/{id}/mappings:
    post:
      summary: Confirm column mapping overrides for a matched audience
      operationId: confirmAudienceMappings
      description: >
        Stores confirmed column mapping overrides for this audience's matched
        member segment. Call this after reviewing the `POST
        /v1/match/{id}/analyze` response to correct any incorrect predictions.
        Sets `mapping_confirmed: true` so the platform skips its own analysis
        and uses these mappings directly.


        If the underlying segment was refreshed with `hitl: true`, the upload is
        still staged — this call also moves the staged file into place and
        starts identity resolution, which is why it can return 500
        `STAGING_COPY_FAILED` if that move fails (the segment is left unchanged
        in that case; retry the call). Use `POST
        /v1/audiences/{id}/mappings/cancel` instead to abort a staged upload
        without confirming.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Audience document ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - column_mappings
              properties:
                column_mappings:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Map of source column name → standard identity attribute
                    name. Use `"(skip)"` as the value to explicitly exclude a
                    column.
      responses:
        "200":
          description: Mappings saved.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                  - column_mappings
                properties:
                  ok:
                    type: boolean
                  column_mappings:
                    type: object
                    additionalProperties:
                      type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          description: >
            STAGING_COPY_FAILED — moving a staged (`hitl: true`) upload into
            place failed; the segment is unchanged, retry the call.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - STAGING_COPY_FAILED
                  message:
                    type: string
  /v1/audiences/{id}/mappings/cancel:
    post:
      summary: Abort a staged (hitl:true) matched-segment upload without confirming
      operationId: cancelAudienceMappings
      description: >
        Audience-scoped counterpart of `POST /v1/segments/{id}/mappings/cancel`
        — resolves this audience's matched member segment and clears its staged
        upload. Performs no file move and no status/mapping-state change — the
        segment is left exactly as it was. Idempotent: returns 200 even if
        nothing is currently staged.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Audience document ID.
      responses:
        "200":
          description: Staged upload cleared (or already clear).
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/deliveries/bulk:
    post:
      summary: Bulk deliver multiple audiences
      operationId: bulkDeliverAudiences
      description: >
        Accepts a list of audience IDs and shared delivery parameters. Fires N
        parallel delivery jobs and returns per-audience results as HTTP 207
        Multi-Status. Partial failure is acceptable — failed items do not roll
        back succeeded ones. Download is the currently active destination;
        LiveRamp and Narrative return 503 before any item or billable record is
        created. Requires 'purchase' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audience_ids
                - destination
              properties:
                audience_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 25
                  description: IDs of audiences to deliver. Max 25 per request.
                destination:
                  type: string
                  enum:
                    - download
                    - liveramp
                    - narrative
                  description: >
                    Shared destination. `liveramp` and `narrative` currently
                    return `DELIVERY_DESTINATION_UNAVAILABLE` without billing.
                template_id:
                  type:
                    - string
                    - "null"
                  description: >
                    Attribute template ID applied to every audience in the
                    batch. Only `standard_iag` is valid. Mutually exclusive with
                    field_list. If neither is provided, the Standard IAG
                    attribute set is used.
                field_list:
                  type:
                    - array
                    - "null"
                  items:
                    type: string
                  description: >
                    Explicit attribute list applied to every audience in the
                    batch — every attribute must be a valid audience attribute.
                    Mutually exclusive with template_id. If neither is provided,
                    the Standard IAG attribute set is used.
                output_format:
                  type: string
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  default: csv
                  description: Shared download artifact format for the batch.
                output_compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    Shared artifact compression. gzip is supported for
                    csv/json/jsonl; Avro uses native DEFLATE and rejects gzip.
                session_id:
                  type:
                    - string
                    - "null"
                  description: Chat session ID for push notification routing.
                campaign_id:
                  type:
                    - string
                    - "null"
                  description: Campaign context for notification routing.
            example:
              audience_ids:
                - aud_abc123
                - aud_def456
              destination: download
              template_id: standard_iag
      responses:
        "207":
          description: >
            Multi-Status — per-audience results. Each entry contains
            `audience_id`, `delivery_id` (if created), `status`, and `error` (if
            failed). Billing capacity errors also expose the same
            machine-readable `code` and USD-denominated capacity details as the
            single-audience delivery endpoint.
          content:
            application/json:
              schema:
                type: object
                required:
                  - results
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      required:
                        - audience_id
                        - status
                      properties:
                        audience_id:
                          type: string
                        delivery_id:
                          type: string
                          description: Present when a delivery was created or found.
                        status:
                          type: string
                          enum:
                            - processing
                            - completed
                            - error
                        error:
                          type: string
                          description: Error message when status is 'error'.
                        code:
                          type: string
                          description: Machine-readable billing code when billing prevents this item.
                          enum:
                            - BILLING_INSUFFICIENT_BALANCE
                            - BILLING_POSTPAY_CEILING_EXCEEDED
                            - BILLING_CONSUMER_POSTPAY_CEILING_EXCEEDED
                            - BILLING_PROJECTION_STALE
                            - BILLING_NOT_READY
                        required:
                          type: number
                          description: Conservative USD requirement for a prepay billing failure.
                        available:
                          type: number
                          description: Effective USD availability for a prepay billing failure.
                        shortfall:
                          type: number
                          description: USD shortfall for a prepay billing failure.
                        accrued:
                          type: number
                          description: Projected USD spend for a postpay ceiling failure.
                        ceiling:
                          type: number
                          description: Configured USD ceiling for a postpay ceiling failure.
                        output_format:
                          type: string
                          enum:
                            - csv
                            - avro
                            - json
                            - jsonl
                          description: Artifact format when an existing or new download delivery is
                            returned.
                        output_compression:
                          type: string
                          enum:
                            - none
                            - gzip
                          description: Artifact compression when an existing or new download delivery is
                            returned.
              example:
                results:
                  - audience_id: aud_abc123
                    delivery_id: dlv_xyz001
                    status: processing
                  - audience_id: aud_def456
                    status: error
                    error: Audience expired on 2026-05-01. Run a refresh first.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/connections/{provider}:
    delete:
      summary: Disconnect an outbound integration
      operationId: deleteConnection
      description: Disconnects the active HubSpot or Klaviyo account using an explicit
        retain-versus-remove policy for app-owned provider data. With `remove`,
        provider cleanup must succeed before the connection is revoked. With
        `retain`, existing provider data remains while future enrichment and
        monitoring stop. Requires 'account' scope and an owner/admin member.
      tags:
        - Integrations
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: provider
          required: true
          description: The outbound provider to disconnect.
          schema:
            type: string
            enum:
              - hubspot
              - klaviyo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - app_data_policy
              properties:
                app_data_policy:
                  type: string
                  enum:
                    - retain
                    - remove
      responses:
        "200":
          description: The provider connection was disconnected.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - disconnected
                properties:
                  disconnected:
                    type: boolean
                    const: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/audiences/{id}/deliveries:
    get:
      summary: List deliveries for an audience
      operationId: listAudienceDeliveries
      description: >
        Returns all deliveries (past and pending) for the given audience, newest
        first. Requires 'discovery' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
      responses:
        "200":
          description: |
            List of deliveries.
          content:
            application/json:
              schema:
                type: object
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: "#/components/schemas/DeliveryObject"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      summary: Create a delivery for an audience
      operationId: createAudienceDelivery
      description: >
        Initiates a delivery of the audience to the specified destination and
        emits usage only after successful egress. Download is currently active.
        LiveRamp and Narrative return 503 before a delivery or billable record
        is created until their partner pushes are implemented. Returns the new
        DeliveryObject — poll `GET /v1/audiences/{id}/deliveries/{delivery_id}`
        for status updates.
              Requires 'purchase' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
        - name: X-Integration-Run-Id
          in: header
          required: false
          schema:
            type: string
            pattern: ^ir_[A-Za-z0-9_-]{40}$
          description: >
            OAuth partnership workflows only. Re-authorizes the private audience
            against the same provider grant and binds delivery, native billing,
            history, and promotion to the exact partnership run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - destination
              properties:
                destination:
                  type: string
                  enum:
                    - download
                    - liveramp
                    - narrative
                  description: >
                    `download` is active. `liveramp` and `narrative` currently
                    return `DELIVERY_DESTINATION_UNAVAILABLE` without billing.
                template_id:
                  type:
                    - string
                    - "null"
                  description: >
                    Enrichment template ID. Only `standard_iag` is valid (see
                    `templates[]` in GET /v1/catalog/fields). Mutually exclusive
                    with `field_list`. If neither `template_id` nor `field_list`
                    is provided, the Standard IAG attribute set (all attributes
                    with `product_usage` containing `audience`) is used.
                field_list:
                  type:
                    - array
                    - "null"
                  items:
                    type: string
                  description: >
                    Explicit list of attribute names to include in the export
                    (alternative to `template_id`) — every attribute must be a
                    valid audience attribute. Mutually exclusive with
                    `template_id`. If neither is provided, the Standard IAG
                    attribute set is used. For matched-subtype rows,
                    individual-level attributes (e.g. `age`, `gender`) come back
                    `null` for a row whose `match_level` doesn't qualify for
                    individual-level data — this is expected, not an error. If
                    `iag_household_id` is requested and available for a row, it
                    is exported in the same org-scoped, non-reversible format as
                    `iag_person_id`.
                include_unmatched:
                  type: boolean
                  default: true
                  description: >
                    **(matched subtype only)** When `true` (default), the export
                    includes rows from your upload that did not resolve to a
                    real graph match — these appear with null enrichment
                    attributes and, in the exported `iag_person_id` column, a
                    best-effort id deterministically derived from the row's own
                    identity signals (or `null` if none qualified) rather than a
                    real platform match. Set to `false` to export resolved
                    records only. Every exported row (matched or not) also
                    carries a `row_id` column matching your original upload, so
                    you can correlate a delivered row back to its source input
                    row regardless of match status. Matched rows additionally
                    carry `match_level` (`I`/`H`/`A`/`S`/`D`), `match_type`
                    (e.g. `graph_name_email_match`, `vector_name_address_match`,
                    `spatial_match`), and `match_confidence` (0–1) — all `null`
                    (not derived) on unmatched rows.
                webhook_url:
                  type: string
                  format: uri
                  description: >
                    Optional HTTPS URL to receive `delivery.completed` or
                    `delivery.failed` events for this delivery. Overrides the
                    org-level webhook URL for this request only; if omitted, the
                    org-configured URL is used as the fallback. Every dispatch
                    is signed regardless of which URL is used — see the Webhooks
                    tag for the envelope shape and signing scheme.
                liveramp_seat_id:
                  type: string
                  description: |
                    (liveramp destination only) Your LiveRamp seat ID.
                narrative_dataset_id:
                  type: string
                  description: |
                    (narrative destination only) Your Narrative.io dataset ID.
                output_format:
                  type:
                    - string
                    - "null"
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  description: >
                    Output file format for download deliveries. Omit to
                    auto-detect: matched audiences default to the format of the
                    original uploaded file (avro → avro, csv → csv, json → json,
                    jsonl → jsonl); all other subtypes default to `csv`. Avro
                    exports preserve native source column types including
                    `REPEATED`/array attributes. Ignored for `liveramp` and
                    `narrative` destinations.
                output_compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    Explicit artifact compression; it never inherits matched
                    input compression. gzip is supported for csv/json/jsonl.
                    Avro uses native DEFLATE and rejects outer gzip. Ignored for
                    non-download destinations.
      responses:
        "201":
          description: |
            Delivery created and queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeliveryObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/audiences/{id}/deliveries/{delivery_id}:
    get:
      summary: Get a single delivery
      operationId: getAudienceDelivery
      description: >
        Returns status, cost, download URL (if completed), and full metadata for
        a single delivery. Poll this endpoint after calling `POST
        /v1/audiences/{id}/deliveries` to track progress.


        **Note on `download_urls`**: for completed `download` deliveries, the
        server regenerates fresh 24-hour presigned URLs on every GET. This means
        repeated calls return different URLs — all pointing to the same file(s).
        Always use the `download_urls` from the most recent response.
              Requires 'discovery' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            Audience document ID.
        - name: delivery_id
          in: path
          required: true
          schema:
            type: string
          description: |
            Delivery document ID.
      responses:
        "200":
          description: |
            Delivery details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeliveryObject"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/audiences/bulk:
    patch:
      summary: Bulk update audiences
      operationId: bulkPatchAudiences
      description: >
        Apply set_logic, segment add/remove, or status changes to multiple
        audiences in a single request. Audiences are subtype-agnostic
        composition wrappers, so this applies uniformly regardless of which
        segment subtypes (filter, matched, similarity, propensity) they compose.
        Fail-closed ownership validation — any unauthorized ID returns 403
        before any writes. Max 50 audiences per call. Returns 207 for partial
        success.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audience_ids
              properties:
                audience_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
                  description: |
                    IDs of audiences to update (1–50).
                set_logic:
                  type: string
                  enum:
                    - union
                    - intersection
                  description: |
                    New set_logic to apply to all specified audiences.
                add_segment_id:
                  type: string
                  description: |
                    Segment ID to append to the included list of each audience.
                remove_segment_id:
                  type: string
                  description: >
                    Segment ID to remove from the included list of each audience.
                status:
                  type: string
                  enum:
                    - archived
                    - active
                  description: >
                    Valid user-settable transitions, applied to every audience
                    in `audience_ids`: `archived` — soft-deletes the audience;
                    excluded from list results by default. `active` — restores a
                    previously archived audience back to active; rejected with
                    400 (`INVALID_STATUS_TRANSITION`) if the current status is
                    not `archived`. System-managed statuses (`pending`,
                    `expired`, `failed`) cannot be set via this endpoint and are
                    rejected with 400.
      responses:
        "200":
          description: |
            All audiences updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: integer
                  failed:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        error:
                          type: string
        "207":
          description: >
            Partial success — at least one audience failed. Check the `failed`
            array.
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: integer
                    description: Number of audiences successfully updated.
                  failed:
                    type: array
                    description: Details for each audience that could not be updated.
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        error:
                          type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    delete:
      summary: Bulk archive (soft-delete) audiences
      operationId: bulkArchiveAudiences
      description: >
        Soft-deletes multiple audiences in a single request. Fail-closed
        ownership validation. Max 50 per call.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audience_ids
              properties:
                audience_ids:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
      responses:
        "200":
          description: |
            Audiences archived.
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                  not_found:
                    type: array
                    items:
                      type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            One or more IDs not owned by caller.
  /v1/audiences/bulk-create:
    post:
      summary: Bulk create audiences
      operationId: bulkCreateAudiences
      description: >
        Create up to 25 composition audiences in a single request. Each item
        wraps one or more pre-existing segments (any subtype — filter, matched,
        similarity, or propensity) via `segment_ids`/`excluded_segment_ids` or
        `segment_refs`; it does not create new segments. To generate a new
        matched/similarity/ propensity *segment* and its wrapper audience in one
        call, use `POST /v1/segments` with `create_audience: true` instead.
        Optionally links all created audiences to a campaign via the
        audience_links sub-collection.
              Requires 'purchase' scope.
      tags:
        - Audiences
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audiences
              properties:
                audiences:
                  type: array
                  minItems: 1
                  maxItems: 25
                  items:
                    type: object
                    required:
                      - name
                    properties:
                      name:
                        type: string
                      segment_refs:
                        type: array
                        description: >
                          Explicit membership specs with role — preferred over
                          segment_ids/excluded_segment_ids when present (takes
                          precedence if both are supplied).
                        items:
                          type: object
                          required:
                            - segment_id
                            - role
                          properties:
                            segment_id:
                              type: string
                            role:
                              type: string
                              enum:
                                - include
                                - exclude
                      segment_ids:
                        type: array
                        items:
                          type: string
                        minItems: 1
                      excluded_segment_ids:
                        type: array
                        items:
                          type: string
                      set_logic:
                        type: string
                        enum:
                          - union
                          - intersection
                      record_count:
                        type: integer
                      visibility:
                        type: string
                        enum:
                          - org
                          - private
                        description: |
                          Visibility of the created audience. Defaults to 'org'.
                campaign_id:
                  type: string
                  description: |
                    Link all created audiences to this campaign.
      responses:
        "201":
          description: |
            Audiences created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
                  audiences:
                    type: array
                    items:
                      $ref: "#/components/schemas/AudienceObject"
                  failed:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        error:
                          type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/segments/bulk-create:
    post:
      summary: Bulk create segments
      operationId: bulkCreateSegments
      description: >
        Create up to 25 filter-subtype segments in one request. Each segment is
        created independently (own `versions/1` snapshot); failures are reported
        per item in `errors` and do not roll back successful items. Returns 201
        when at least one segment was created, 400 (same body shape) when every
        item failed. Use `POST /v1/segments` individually for
        matched/similarity/propensity segments.
              Requires 'purchase' scope.

        Each item's `filters`/`filter_groups` are validated against the live
        Field Catalog before it's created — an unknown field, a disallowed
        operator, or an invalid enum value fails that item only, reported in
        `errors` (same as any other per-item failure). If the Field Catalog
        itself is temporarily unreachable, the entire request fails with 502
        `FILTER_CATALOG_UNAVAILABLE` rather than creating some segments
        unvalidated.
      tags:
        - Segments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - segments
              properties:
                segments:
                  type: array
                  minItems: 1
                  maxItems: 25
                  items:
                    type: object
                    required:
                      - name
                    properties:
                      name:
                        type: string
                        minLength: 1
                        maxLength: 120
                        description: Display name for the segment.
                      subtype:
                        type: string
                        enum:
                          - filter
                          - matched
                          - similarity
                          - propensity
                        default: filter
                        description: >
                          Only 'filter' is supported by bulk-create — any other
                          value fails per-item with an entry in `errors`.
                      filters:
                        type: array
                        items:
                          $ref: "#/components/schemas/Filter"
                        description: Legacy flat filter list.
                      filter_groups:
                        type: array
                        items:
                          $ref: "#/components/schemas/FilterGroup"
                        description: Filter groups defining the segment criteria.
                      count_snapshot:
                        type: number
                        minimum: 0
                        description: Optional known record count to seed the segment with.
                      create_audience:
                        type: boolean
                        default: false
                        description: >
                          When true, auto-creates a thin audience wrapper for
                          this segment (returned as `audience_id` on the created
                          segment).
      responses:
        "201":
          description: |
            At least one segment created. Check `errors` for partial failures.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkCreateSegmentsResult"
        "400":
          description: >
            Every input item failed (`created: 0`) — same body shape as 201 with
            per-item reasons in `errors` — or the request body failed validation
            (standard error shape).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/BulkCreateSegmentsResult"
                  - $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "502":
          description: Field Catalog temporarily unreachable — no items were validated or
            created (code FILTER_CATALOG_UNAVAILABLE).
  /v1/catalog/fields:
    get:
      summary: Attribute catalog with pricing
      operationId: getCatalogFields
      description: >
        Returns the list of available enrichment attributes — every attribute
        with `product_usage` containing `audience` (the definition of a valid
        user-facing IAG attribute) — with coverage rates and per-attribute
        pricing, plus the single system delivery template. Use this to browse
        available attributes before creating a delivery.
              Requires 'discovery' scope.
      tags:
        - Enrichment
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Attribute catalog.
          content:
            application/json:
              schema:
                type: object
                properties:
                  fields:
                    type: array
                    description: |
                      Available enrichment attributes from the column registry.
                    items:
                      type: object
                      properties:
                        field_name:
                          type: string
                          description: |
                            Programmatic attribute name to use in `field_list`.
                        label:
                          type: string
                          description: |
                            Human-readable display label.
                        data_type:
                          type: string
                          description: |
                            Source data type (e.g. STRING, INT64, FLOAT64).
                        coverage_rate:
                          type: number
                          description: >
                            Fraction of records that have a value for this
                            attribute (0.0–1.0).
                  templates:
                    type: array
                    description: >
                      Always exactly one entry — the Standard IAG template,
                      usable as a `template_id` shortcut. Its `fields` is the
                      full audience attribute set above, resolved live from the
                      registry (not a hardcoded list).
                    items:
                      type: object
                      properties:
                        template_id:
                          type: string
                          enum:
                            - standard_iag
                          description: >
                            ID to pass as `template_id` in delivery or quote
                            requests.
                        label:
                          type: string
                          description: |
                            Human-readable template name.
                        fields:
                          type: array
                          items:
                            type: string
                          description: |
                            Attribute names included in this template.
                    example:
                      - template_id: standard_iag
                        label: Standard IAG
                        fields:
                          - age
                          - gender
                          - estimated_household_income
                  pricing:
                    type: object
                    description: |
                      Effective per-record pricing for the calling org.
                    properties:
                      base_record_price:
                        type: number
                        description: |
                          Base cost per record (USD).
                      matched_record_price:
                        type: number
                        description: |
                          Cost per resolved record for matched audiences (USD).
                      micro_batch_price_per_record:
                        type: number
                        description: Cost per resolved record for synchronous micro-batch matching
                          (USD).
                      field_surcharges:
                        type: object
                        description: >
                          Additional per-record cost for specific attributes
                          (field_name → USD).
                        additionalProperties:
                          type: number
                      delivery_surcharges:
                        type: object
                        description: >
                          Per-record surcharge by destination (destination →
                          USD).
                        additionalProperties:
                          type: number
                      usage_discount_bps:
                        type: integer
                        minimum: 0
                        maximum: 10000
                        description: Contract usage-rate discount in basis points; commitment charges
                          and minimums are unchanged.
                      projected_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                    required:
                      - base_record_price
                      - matched_record_price
                      - micro_batch_price_per_record
                      - field_surcharges
                      - delivery_surcharges
                      - usage_discount_bps
                      - projected_at
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/data-release:
    get:
      summary: Current data release version
      operationId: getDataRelease
      description: >
        Returns the platform's current data-release version — the same value
        `POST /v1/match`'s per-row `resolution_action: "current"` label compares
        a previously-delivered identity's stored refresh-major against.
        Cacheable: backed by a cache refreshed roughly every 15 minutes, not a
        live query.
              Requires 'discovery' scope.
      tags:
        - Enrichment
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Current data-release version.
          content:
            application/json:
              schema:
                type: object
                properties:
                  current_release_major:
                    type: integer
                    description: >
                      Current full-warehouse-rebuild release version. A
                      previously-delivered identity's stored refresh-major equal
                      to this value is guaranteed unchanged since last
                      delivered.
                  current_release_minor:
                    type: integer
                    description: Current incremental-delta release version within the major release.
                  as_of:
                    type: string
                    format: date-time
                    description: When this snapshot was last refreshed.
                required:
                  - current_release_major
                  - current_release_minor
                  - as_of
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "503":
          description: |
            The data-release registry has not been populated yet. Retry shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/match:
    post:
      summary: Micro-batch synchronous match
      operationId: matchMicroBatch
      description: >
        Synchronous inline matching for small record sets (up to 100 records).
        Submit your records and the list of attributes you want appended, and
        receive matched results immediately.


        **Person IDs:** A real match returns a tier `01` `iag_person_id`: a
        stable, non-reversible ID salted for your organization. The same real
        identity is consistent inside your organization and different in every
        other organization. An unmatched row may receive a derived tier
        `02`–`05` ID from its own submitted signals. That derived value is a
        continuity label—not a graph match, licensed identity, refresh key, or
        resolved audience member. Use the match metadata and `match_count`, not
        ID presence alone, to identify real matches.


        **Billing:** Usage is emitted per matched record only (unmatched records
        are free). The platform reserves a conservative ceiling and durably
        queues finalized usage for billing rather than debiting a local balance
        field. `amount_charged` is the conservative USD estimate accepted for
        provider delivery; it is not a finalized invoice amount.


        **Records array:** one output record per input record — matched or not.
        Unmatched rows carry `match_level`/`match_type`/`match_confidence: null`
        and a best-effort `iag_person_id` derived from the row's own identity
        signals (or `null` if none qualified) — only real matches are billed.
        Use `match_count` vs `record_count` to distinguish real matches from
        derived-only rows.


        For datasets larger than 100 records, use `POST /v1/match/file` instead.
        Upload, analysis, and async matching are free until egress. The first
        successful delivery involving that file-match run emits its one
        aggregate match usage set together with normal delivery usage.


        **Retries:** pass an `Idempotency-Key` header to make a retried call
        safe to repeat without a duplicate charge. The same key with an
        identical body returns the original response verbatim, at zero
        additional charge (honored for 24 hours); the same key with a different
        body is rejected with 409 `IDEMPOTENCY_KEY_CONFLICT`. Without this
        header, every call reserves and charges independently — recommended for
        any client that may retry on timeout.
              Requires 'purchase' scope.
      tags:
        - Enrichment
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 8
            maxLength: 160
            pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{7,159}$
          description: >
            Optional. Makes a retried call safe to repeat — see the endpoint
            description's **Retries** section.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - records
              properties:
                records:
                  type: array
                  minItems: 1
                  maxItems: 100
                  description: >
                    Records to match (max 100). Each object should include at
                    least one identity signal: `email` (or `email_sha256`),
                    `phone` (or `phone_sha256`), `full_name` (or
                    `first_name`+`last_name`), or address attributes
                    (`address_1`, `city`, `state`, `zip`). All attributes are
                    optional — include only what you have. Any extra attributes
                    you include are echoed back unchanged.
                  items:
                    type: object
                    additionalProperties: true
                    properties:
                      email:
                        type: string
                        format: email
                        description: Primary email address.
                      phone:
                        type: string
                        description: Phone number in any standard format.
                      email_sha256:
                        type: string
                        description: >
                          Lowercase-hex SHA-256 hash of a normalized email
                          address, for clients whose own systems never expose
                          raw email addresses (e.g. data co-op / clean-room
                          partners using the same hashing convention). The
                          platform passes this through without re-hashing. Do
                          not submit an already-hashed value in `email` — it
                          would be hashed a second time and never match.
                      phone_sha256:
                        type: string
                        description: >
                          Lowercase-hex SHA-256 hash of a normalized phone
                          number. Same pre-hashed semantics as `email_sha256`.
                      full_name:
                        type: string
                        description: >
                          Optional convenience field. When provided (and
                          first_name / last_name are absent), the engine parses
                          this into first_name, middle_name, last_name, and
                          name_suffix automatically. The parsed components are
                          used for matching; the original full_name value is
                          echoed back unchanged.
                      first_name:
                        type: string
                      middle_name:
                        type: string
                      last_name:
                        type: string
                      name_suffix:
                        type: string
                        description: Generational suffix (e.g. `JR`, `SR`, `III`).
                      emails:
                        type: array
                        items:
                          type: string
                          format: email
                        description: >
                          Additional raw email addresses beyond `email`, tried
                          as extra match permutations.
                      emails_sha256:
                        type: array
                        items:
                          type: string
                        description: Pre-hashed counterpart to `emails`.
                      phones:
                        type: array
                        items:
                          type: string
                        description: >
                          Additional raw phone numbers beyond `phone`, tried as
                          extra match permutations.
                      phones_sha256:
                        type: array
                        items:
                          type: string
                        description: Pre-hashed counterpart to `phones`.
                      addresses:
                        type: array
                        items:
                          type: object
                          properties:
                            address_1:
                              type: string
                            address_2:
                              type: string
                            city:
                              type: string
                            state:
                              type: string
                            zip:
                              type: string
                        description: >
                          Additional addresses beyond
                          `address_1`/`city`/`state`/`zip`, tried as extra match
                          permutations.
                      address_1:
                        type: string
                        description: Street address line 1.
                      address_2:
                        type: string
                        description: Street address line 2 (apt, suite, etc.).
                      city:
                        type: string
                      state:
                        type: string
                        description: Two-letter US state code.
                      zip:
                        type: string
                        description: 5- or 9-digit ZIP code.
                      dob:
                        type: string
                        description: Date of birth — any parseable date format (e.g. YYYY-MM-DD).
                      iag_person_id:
                        type: string
                        description: >
                          A previously-issued tier `01` person ID from this
                          organization (format `CLIENTCODE_TIER_HASH`, e.g.
                          returned by a prior call to this endpoint or a
                          matched-segment delivery). A valid ID can go directly
                          to refresh without repeating identity matching. A
                          foreign, malformed, or derived tier `02`–`05` ID
                          cannot be used for direct refresh; the row falls back
                          to its other identity signals, if any.
                field_list:
                  type: array
                  items:
                    type: string
                  description: >
                    Enrichment attribute names to append on match — every
                    attribute must be a valid audience attribute (see `GET
                    /v1/catalog/fields`). Mutually exclusive with `template_id`.
                    If neither is provided, the Standard IAG attribute set (all
                    attributes with `product_usage` containing `audience`) is
                    used. Individual-level attributes (e.g. `age`, `gender`)
                    come back `null` per row when that row's `match_level`
                    doesn't qualify for individual-level data (see `match_level`
                    below) — this is expected, not an error. Request only the
                    attributes valid for the `match_level`(s) you accept if you
                    want to avoid nulls.
                template_id:
                  type: string
                  description: >
                    Pre-built enrichment bundle ID. Only `standard_iag` is valid
                    (see `templates[]` in `GET /v1/catalog/fields`). Mutually
                    exclusive with `field_list`. If neither is provided, the
                    Standard IAG attribute set is used.
                match_level:
                  type: array
                  items:
                    type: string
                    enum:
                      - I
                      - H
                      - D
                      - S
                      - A
                  description: >
                    Filter results to specific match levels. `I` = individual,
                    `H` = household, `D` = digital (email/phone only match), `S`
                    = spatial (nearby-address proximity match), `A` =
                    address-level. Defaults to all 5 levels (`['I', 'H', 'D',
                    'S', 'A']`) if omitted. Restricting this also restricts
                    which attributes come back populated — see `field_list`
                    above.
                create_scratch_segment:
                  type: boolean
                  default: false
                  description: >
                    When true, materialize the real matched identities from this
                    completed response as a reusable 24-hour matched scratch
                    segment. This reuses the already-paid result and does not
                    run matching or billing again. The response's
                    `scratch_segment` is null when no identities matched.
      responses:
        "200":
          description: |
            Matched records.
          content:
            application/json:
              schema:
                type: object
                properties:
                  records:
                    type: array
                    description: >
                      One output record per input record — matched or not.
                      Matched rows carry a resolved `iag_person_id`,
                      `match_level`/`match_type`/`match_confidence`, and (when
                      requested) enrichment attributes. Unmatched rows carry
                      `match_level`/`match_type`/`match_confidence: null` and a
                      best-effort derived `iag_person_id` (see `iag_person_id`
                      below) computed from the row's own identity signals, or
                      `null` if none qualified. Use `match_count` vs
                      `record_count` to distinguish real matches from
                      derived-only rows — only real matches are billed. Input
                      attributes are echoed back unchanged; resolved enrichment
                      attributes are added alongside them.
                    items:
                      type: object
                      additionalProperties: true
                      properties:
                        row_id:
                          type: string
                          description: >
                            Correlates a matched output row back to its position
                            in the submitted records array (1-indexed as a
                            string when the input didn't supply one).
                        email:
                          type: string
                          format: email
                        phone:
                          type: string
                        email_sha256:
                          type: string
                          description: >
                            Present when the request included `email_sha256`,
                            echoed back unchanged.
                        phone_sha256:
                          type: string
                          description: >
                            Present when the request included `phone_sha256`,
                            echoed back unchanged.
                        full_name:
                          type:
                            - string
                            - "null"
                          description: >
                            Present when the request included full_name; the
                            original submitted value, echoed back unchanged.
                            Null when the request did not include one.
                        first_name:
                          type: string
                        middle_name:
                          type: string
                          description: >
                            Echoed back when present on input, or populated from
                            `full_name` parsing when the request supplied
                            `full_name` without `first_name`/`last_name`.
                        last_name:
                          type: string
                        name_suffix:
                          type: string
                          description: >
                            Generational suffix (e.g. `JR`, `SR`, `III`). Echoed
                            back when present on input, or populated from
                            `full_name`/`last_name` parsing.
                        address_1:
                          type: string
                        address_2:
                          type: string
                        city:
                          type: string
                        state:
                          type: string
                        zip:
                          type: string
                        dob:
                          type: string
                        emails:
                          type: array
                          items:
                            type: string
                            format: email
                          description: Additional raw emails from the request, echoed back unchanged.
                        emails_sha256:
                          type: array
                          items:
                            type: string
                          description: Additional pre-hashed emails from the request, echoed back
                            unchanged.
                        phones:
                          type: array
                          items:
                            type: string
                          description: Additional raw phones from the request, echoed back unchanged.
                        phones_sha256:
                          type: array
                          items:
                            type: string
                          description: Additional pre-hashed phones from the request, echoed back
                            unchanged.
                        addresses:
                          type: array
                          items:
                            type: object
                            additionalProperties: true
                          description: Additional addresses from the request, echoed back unchanged.
                        iag_person_id:
                          type:
                            - string
                            - "null"
                          description: >
                            Org-scoped, non-reversible identifier — never a raw
                            internal id. The same real identity receives a
                            consistent tier-01 ID within one organization and a
                            different ID in every other organization. Format:
                            `CLIENTCODE_TIER_HASH`. `TIER` is `01` for a real
                            graph match (see
                            `match_level`/`match_type`/`match_confidence`) or
                            `02`–`05` for a best-effort id deterministically
                            derived from the row's own identity signals when it
                            did NOT match (name+address, email, name+phone, or
                            name+zip, in that priority order). `null` when the
                            row is unmatched and no identity signal qualified
                            for derivation. A tier-01 id may be resubmitted as
                            input (see `iag_person_id` on the request schema
                            above) to re-resolve the same match; derived (tier
                            02–05) ids cannot be.
                        match_level:
                          type: string
                          enum:
                            - I
                            - H
                            - A
                            - S
                            - D
                          description: >
                            Granularity of the match. `I` = individual, `H` =
                            household, `A` = address-level, `S` = spatial
                            (nearby-address proximity match), `D` = digital
                            (email/phone only match). Present only on matched
                            records (`null`, not a derived value, on unmatched
                            rows).
                        match_type:
                          type: string
                          description: >
                            Matching strategy that produced the result (e.g.
                            `graph_name_email_match`,
                            `vector_name_address_match`, `spatial_match`).
                            Present only on matched records.
                        match_confidence:
                          type: number
                          description: >
                            Confidence score for the match (0–1). Present only
                            on matched records.
                        enrichment:
                          type: object
                          additionalProperties: true
                          description: >
                            Requested enrichment attributes keyed by column name
                            (e.g. `acs_housing_units`,
                            `usda_median_hh_income_2023`). Only present when
                            `field_list` or `template_id` was provided and the
                            record matched.
                        resolution_action:
                          type: string
                          enum:
                            - resolved
                            - refreshed
                            - current
                            - unmatched
                            - identity_unavailable
                          description: >
                            Customer-facing resolution outcome for this row,
                            orthogonal to `license_action` below. `resolved` =
                            an organic PII match (no id submitted for this row).
                            `refreshed` = the caller submitted a valid
                            `iag_person_id` for this row (direct Refresh) and
                            the platform cannot confirm this identity's
                            enrichment is unchanged since it was last delivered
                            — whether or not this call was also billed (see
                            `license_action`). `current` = same as `refreshed`,
                            but the platform's data-release registry (see `GET
                            /v1/data-release`) confirms that the identity
                            remains on the same refresh-major release as its
                            last delivery. The response contains current-mart
                            values for the fields requested in this call; the
                            requested field set may differ from an earlier call.
                            `unmatched` = no real match, never licensed.
                            `identity_unavailable` = a previously valid seeded
                            id no longer resolves to deliverable data (deletion,
                            suppression, retirement, or a split graph identity —
                            the underlying reason is deliberately never
                            distinguished here); the record otherwise looks
                            exactly like an ordinary unmatched row. The
                            platform's full vocabulary also defines `invalid`,
                            reserved for a reject-the-row policy this API does
                            not yet implement — it is not returned today, but a
                            client should not treat its future appearance as a
                            breaking change.
                        license_action:
                          type: string
                          enum:
                            - none
                            - started
                            - renewed
                          description: >
                            License effect of this row's resolution, independent
                            of `resolution_action` — a row can simultaneously be
                            `resolution_action: resolved` and `license_action:
                            renewed`. `none` = no license period was started or
                            renewed this call (already active, unmatched, or
                            identity_unavailable). `started` = this identity had
                            no prior confirmed license claim and one was
                            started. `renewed` = a prior confirmed claim existed
                            (even if expired) and the period was renewed.
                        license_expires_at:
                          type:
                            - string
                            - "null"
                          description: >
                            ISO-8601 timestamp the identity's current license
                            period expires. Populated when reusing an existing
                            active license (`license_action: none`) and for a
                            row starting or renewing a period in this same call
                            (`license_action: started` or `renewed` — a fixed 12
                            months from now). Null when no license period
                            applies (e.g. an unmatched or identity_unavailable
                            row).
                  record_count:
                    type: integer
                    description: |
                      Total number of input records processed.
                  match_count:
                    type: integer
                    description: |
                      Number of records that resolved to a known identity.
                  billable_identity_count:
                    type: integer
                    description: >
                      Number of unique real identities charged for this
                      operation, deduplicated by internal identity rather than
                      row count. Duplicate rows resolving to the same identity
                      are counted once; a resubmitted `iag_person_id` for an
                      identity with an active license contributes zero (free
                      Refresh). May be less than or equal to `match_count`, and
                      is the value actually billed — `match_count` describes
                      matching, not billing.
                  match_rate:
                    type: number
                    description: |
                      match_count / record_count.
                  amount_charged:
                    type: number
                    description: >
                      Conservative platform estimate in USD for usage accepted
                      for delivery (e.g. `0.30` means $0.30 estimated). This
                      legacy-named field is not a local credit debit or
                      finalized invoice amount; unmatched records emit no usage.
                  run_id:
                    type: string
                    description: >
                      Unique identifier for this match run. Pass to `GET
                      /v1/match/runs/{runId}` to retrieve full details or audit
                      this call later. Requires 'discovery' scope to read.
                  scratch_segment:
                    oneOf:
                      - type: object
                        additionalProperties: false
                        required:
                          - segment_id
                          - name
                          - record_count
                          - ephemeral
                          - expires_at
                        properties:
                          segment_id:
                            type: string
                          name:
                            type: string
                          record_count:
                            type: integer
                            minimum: 1
                          ephemeral:
                            type: boolean
                            const: true
                          expires_at:
                            type: string
                            format: date-time
                      - type: "null"
                    description: >
                      The reusable matched scratch segment created from this
                      call when `create_scratch_segment` is true; null when
                      there were no real matches.
                  scratch_segment_error:
                    type: object
                    additionalProperties: false
                    required:
                      - code
                      - message
                    properties:
                      code:
                        type: string
                        const: SCRATCH_SEGMENT_CREATION_FAILED
                      message:
                        type: string
                    description: >
                      Present only when matching and billing succeeded but the
                      optional, non-billable scratch-segment materialization
                      failed.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          description: >
            BILLING_NOT_READY — the billing account/route isn't active.
            `IDEMPOTENCY_KEY_CONFLICT` — the `Idempotency-Key` header was
            already used with a different request body.
            `IDEMPOTENCY_REQUEST_IN_PROGRESS` — another request with this key is
            in flight; retry after the seconds in `Retry-After`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - BILLING_NOT_READY
                      - IDEMPOTENCY_KEY_CONFLICT
                      - IDEMPOTENCY_REQUEST_IN_PROGRESS
                  message:
                    type: string
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/integration-runs:
    get:
      summary: List retained partnership activity
      operationId: listIntegrationRuns
      description: >
        Returns safe aggregate summaries for batch/file partnership workflows.
        Raw workflow objects, identity manifests, row data, and signed URLs are
        never returned. Use at most one optional filter. Requires 'discovery'
        scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      parameters:
        - name: provider
          in: query
          schema:
            type: string
        - name: connection_id
          in: query
          schema:
            type: string
        - name: kind
          in: query
          schema:
            type: string
            enum:
              - batch_enrichment
              - file_enrichment
              - ingest
              - activation
              - export
        - name: status
          in: query
          schema:
            type: string
            enum:
              - created
              - processing
              - awaiting_boundary
              - settling
              - completed
              - failed
              - cancelled
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Retained partnership activity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationRunListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/integration-runs/{runId}:
    get:
      summary: Get retained partnership activity
      operationId: getIntegrationRun
      description: >
        Returns one safe partnership-run projection without private artifact or
        manifest references. Requires 'discovery' scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            pattern: ^ir_[A-Za-z0-9_-]{40}$
      responses:
        "200":
          description: Partnership activity detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationRun"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/integration-match-collections:
    get:
      summary: List saved integration match collections
      operationId: listIntegrationMatchCollections
      description: Lists private provider-neutral holding collections containing
        already-settled integration matches. Requires 'discovery' scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Saved integration match collections
          content:
            application/json:
              schema:
                type: object
                required:
                  - collections
                properties:
                  collections:
                    type: array
                    items:
                      $ref: "#/components/schemas/IntegrationMatchCollection"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/integration-match-collections/{collectionId}/materializations/preview:
    post:
      summary: Preview a saved-match segment materialization
      operationId: previewIntegrationMatchMaterialization
      description: Returns exact deduplication, privacy, overlap, and net-addition
        counts without mutation or usage. Requires 'discovery' scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IntegrationMaterializationPreviewRequest"
      responses:
        "200":
          description: Exact materialization preview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationMaterializationPreviewResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/integration-match-collections/{collectionId}/materializations:
    post:
      summary: Build or extend a segment from saved integration matches
      operationId: createIntegrationMatchMaterialization
      description: Idempotently creates a matched segment or extends a compatible
        standalone matched segment without rerunning matching, creating an
        audience, or recording usage. Requires 'purchase' scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 240
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IntegrationMaterializationRequest"
      responses:
        "200":
          description: Existing idempotent materialization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationMaterialization"
        "201":
          description: Materialization completed and the segment is available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationMaterialization"
        "202":
          description: Materialization intent is durable and queued for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationMaterialization"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/integration-match-materializations/{materializationId}:
    get:
      summary: Get a saved-match segment materialization
      operationId: getIntegrationMatchMaterialization
      description: Returns safe aggregate materialization status. Requires 'discovery'
        scope.
      tags:
        - Partnerships
      security:
        - bearerAuth: []
      parameters:
        - name: materializationId
          in: path
          required: true
          schema:
            type: string
            pattern: ^imm_[a-f0-9]{40}$
      responses:
        "200":
          description: Saved-match materialization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationMaterialization"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/match/file:
    post:
      summary: Start an async file-based identity resolution job
      operationId: createFileMatch
      description: >
        Creates a **matched segment** by uploading a customer list for identity
        resolution. Equivalent to `POST /v1/segments` with `subtype: matched`.


        Upload csv, newline-delimited json/jsonl, or Avro records to the
        Infinite Audience identity graph and receive an enriched file back. Each
        format may be raw or gzip. Useful for CRM onboarding, suppression list
        resolution, contact data hygiene, and data co-op workflows.


        **File structure:** there is no fixed column schema — column names are
        inferred via AI (`POST /v1/match/{segment_id}/analyze` reports the
        mapping, a confidence score per column, and any warnings). For the best
        match quality, name your columns (or use names that closely resemble)
        the standard identity attributes: `email`, `phone`, `full_name` (or
        separately, `first_name`, `middle_name`, `last_name`, `name_suffix`),
        `address_1`, `address_2`, `city`, `state`, `zip`, `dob`, `iag_person_id`
        (a re-upload of an id previously returned by this platform — format
        `CLIENTCODE_TIER_HASH` — resolved server-side before matching). Only map
        a column to `full_name` when the file has a single combined-name column
        — if separate first/last name columns exist, map those instead. Only a
        valid tier `01` ID previously issued to this organization can go
        directly to refresh. Derived tier `02`–`05` IDs are continuity labels
        for unmatched rows, not resolved identities; submit the row's identity
        signals again if you want to attempt resolution. A column that can't be
        confidently mapped is excluded from identity resolution — if a critical
        attribute (`email`, `phone`, `last_name`) ends up unmapped, match
        accuracy drops or the segment may fail to resolve at all.


        **Pre-hashed identity columns:** for clients whose own systems never
        expose raw email/phone values, map a column to `email_sha256` or
        `phone_sha256` (lowercase-hex SHA-256 digests) instead of the raw
        `email`/`phone` target — the platform passes these through without
        re-hashing. Do not map an already-hashed column to the raw
        `email`/`phone` target (it would be hashed a second time and never
        match), and do not map a raw email/phone column to
        `email_sha256`/`phone_sha256` just because of its name — the mapper
        trusts sample-value shape over column name. `avro`/`json`/ `jsonl`
        uploads (which support array columns) may also supply `emails`,
        `phones`, `addresses`, `emails_sha256`, and `phones_sha256` as extra
        match permutations beyond the primary identity fields. If your upload
        includes `row_id`, every value must be unique across all shards. When it
        is absent, the platform assigns stable `row_0`, `row_1`, ... values.
        Duplicate ids or collisions with generated values fail the match before
        its result can be delivered.


        **Billing:** Upload, analysis, and matching completion emit no billable
        usage. The first successful `POST /v1/audiences/{id}/deliveries` or
        `POST /v1/match/{id}/deliveries` involving the current run emits its one
        aggregate `platform_match` usage set together with audience,
        destination, and enrichment usage. Retries, duplicates, and later
        deliveries do not emit that file-match usage again.


        **Workflow (hitl unset/false — default, upload resolves
        automatically):**

        1. **POST /v1/match/file** — receive a `segment_id` and a 30-minute
        presigned
           `upload_url`. Pass `file_format` to specify your file type (default `csv`).
           When `create_audience: true` (default), also receives an `audience_id`.

        2. **Upload your file** — `PUT` to `upload_url` with the underlying
           format's Content-Type. For gzip, send raw gzip bytes and do not set
           Content-Encoding. No `Authorization` header is needed.

        3. *(Optional)* **`POST /v1/match/{segment_id}/analyze`** — preview
        column
           mapping. Note that by the time this can return real data, the upload has
           already triggered automatic resolution — this step does not pause anything.

        4. **Automatic column analysis** — the platform always runs column
        analysis as part
           of the match workflow. If no usable identity attributes can be resolved, the
           segment transitions to `failed` and `error_message` describes the issue.

           **Standard column names for best results:** `email`, `phone`, `full_name`,
           `first_name`, `middle_name`, `last_name`, `name_suffix`, `address_1`,
           `address_2`, `city`, `state`, `zip`, `dob`, `iag_person_id`. Pre-hashed
           `email_sha256`/`phone_sha256` are also supported — see below.

        5. Poll **`GET /v1/match/file/{match_id}`** until `matching_status:
        completed` (or use a
           `segment.ready` webhook).

        6. **`POST /v1/match/{match_id}/deliveries`** — export the already
        resolved result.
           Poll **`GET /v1/match/{match_id}/deliveries`** for status; it takes the match id
           directly and needs no audience id — if you only want matching/enrichment and
           don't need audience-building concepts, this and step 1's `segment_id`/`match_id`
           are the only ids you need to track end to end. (The audience-scoped
           `POST /v1/audiences/{audience_id}/deliveries` also works and is what this endpoint
           forwards to internally, but requires the audience id from step 1.)


        **Workflow (`hitl: true` — requires an explicit confirm before
        resolution runs):**

        1. **POST /v1/match/file** `{"hitl": true}` — receive a `segment_id` and
        `upload_url`.

        2. **Upload your file** — lands at a staging location, NOT the path that
           triggers automatic resolution.

        3. **`POST /v1/match/{segment_id}/analyze`** — reads the staged file;
        safe to
           call any time after upload, since nothing has started yet.

        4. **`POST /v1/segments/{segment_id}/mappings`** to confirm (moves the
        file
           into place and starts resolution) or
           **`POST /v1/segments/{segment_id}/mappings/cancel`** to abort (nothing
           ever ran).

        5. Poll **`GET /v1/match/file/{match_id}`** until `matching_status:
        completed`, then
           **`POST /v1/match/{match_id}/deliveries`** — same match-namespaced export and
           polling as the default flow above (see step 6 there).


        **Sharded uploads:** set `shard_count` > 1 for same-format files. The
        response returns index-ordered `upload_urls`; upload every declared
        shard. Every CSV shard must include the same header row, and all shards
        must share schema and compression. Analysis samples shard 0; a later
        mismatch fails the job explicitly.


        **Retries:** pass an `Idempotency-Key` header to make a retried create
        call safe to repeat: the same key with an identical body returns the
        original segment/audience (and freshly re-signed upload URL(s), if the
        originals already expired) instead of creating a duplicate. Unlike `POST
        /v1/match`, this endpoint isn't billed at creation, so a duplicate
        without this header is an orphan Library entry, not a double charge —
        recommended for any client that may retry.
              Requires 'purchase' scope.
      tags:
        - Enrichment
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 8
            maxLength: 160
            pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{7,159}$
          description: >
            Optional. Makes a retried create call safe to repeat — see the
            endpoint description's **Retries** section.
        - name: X-Integration-Execution-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 8
            maxLength: 220
            pattern: ^[A-Za-z0-9:_-]{8,220}$
          description: >
            OAuth partnerships only. A provider-stable execution key that
            creates or replays a private partnership run. Use the returned
            integration_run_id for status and delivery requests.
      requestBody:
        required: false
        description: >
          All attributes are optional. You may omit the request body entirely or
          send `{}` — both are equivalent and will create a CSV match job with
          an auto-generated name.
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: >
                    Optional label for this match job. Defaults to `"File Match
                    — {ISO date}"` if omitted.
                file_format:
                  type: string
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  default: csv
                  description: >
                    Input file format. `csv` (default) treats every column as a
                    single scalar value — multi-value/array attributes (email,
                    phone, address_1, etc.) are not supported in csv. `avro`
                    supports native array columns — use it when your file
                    already encodes multi-value attributes as proper arrays.
                    `json` and `jsonl` also support native array columns and are
                    parsed identically — newline-delimited JSON, one record
                    object per line (not a single top-level JSON array).
                compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    gzip means the complete underlying file is a gzip stream.
                    Upload raw gzip bytes with the underlying format's
                    Content-Type and no Content-Encoding. gzip is not supported
                    with file_format=avro (avro is already internally
                    compressed) — rejected with 400
                    UNSUPPORTED_INPUT_COMPRESSION.
                create_audience:
                  type: boolean
                  default: true
                  description: >
                    When true (default), automatically creates a thin audience
                    wrapper referencing the new matched segment. The
                    `audience_id` is returned in the response and can be used
                    for delivery immediately once the segment is active.
                campaign_id:
                  type: string
                  description: >
                    Optional. If provided and create_audience is true, the
                    created audience is automatically linked to this campaign on
                    creation. Omit if you are not using campaign workspaces.
                webhook_url:
                  type: string
                  format: uri
                  description: >
                    Optional HTTPS URL to receive segment.ready or
                    segment.failed events when the identity matching job
                    completes. Overrides the org-level webhook URL for this
                    request only.
                hitl:
                  type: boolean
                  default: false
                  description: >
                    Requires an explicit confirmation step before identity
                    resolution runs — see the two workflow sequences above.
                    Defaults to false (upload alone resolves automatically, the
                    existing behavior).
                match_level:
                  type: array
                  items:
                    type: string
                    enum:
                      - I
                      - H
                      - D
                      - S
                      - A
                  description: >
                    Filter which match levels are accepted for this job. `I` =
                    individual, `H` = household, `D` = digital (email/phone only
                    match), `S` = spatial (nearby-address proximity match), `A`
                    = address-level. Defaults to all 5 levels (`['I', 'H', 'D',
                    'S', 'A']`) if omitted. This only affects which rows match
                    and at what level — field selection
                    (`field_list`/`template_id`) happens separately, per
                    delivery (`POST /v1/audiences/{id}/deliveries` or `POST
                    /v1/match/{id}/deliveries`), where individual-level
                    attributes are nulled out per row for rows that didn't match
                    at a level that qualifies for individual-level data.
                shard_count:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 1
                  description: >
                    Set > 1 for same-format shards. Every CSV shard must include
                    the same header row, and all shards must share schema and
                    compression. Returns upload_urls instead of upload_url.
            examples:
              minimal:
                summary: Minimal request — omit the body entirely or send `{}` (CSV, auto-named)
                value: {}
              named:
                summary: Named match job — CSV
                value:
                  name: Q2 2026 CRM Refresh
              with_campaign:
                summary: Match job linked to a campaign
                value:
                  name: Q2 2026 CRM Refresh
                  campaign_id: camp_abc123
      responses:
        "201":
          description: >
            File match job created — upload your file to the returned upload_url
            to proceed.
          content:
            application/json:
              schema:
                type: object
                required:
                  - match_id
                  - segment_id
                  - name
                  - status
                  - upload_expires_at
                  - expires_at
                  - file_format
                  - compression
                properties:
                  match_id:
                    type: string
                    description: Backward-compatible alias of segment_id.
                  segment_id:
                    type: string
                    description: >
                      Unique ID for this matched segment. Poll `GET
                      /v1/segments/{segment_id}` until status is active.
                  audience_id:
                    type: string
                    description: >
                      ID of the auto-created audience wrapper. Present when
                      create_audience is true. Use for deliveries once the
                      segment is active.
                  integration_run_id:
                    type: string
                    pattern: ^ir_[A-Za-z0-9_-]{40}$
                    description: >
                      Present when X-Integration-Execution-Key created a private
                      OAuth partnership run.
                  name:
                    type: string
                    description: |
                      Label assigned to the job (supplied or auto-generated).
                  status:
                    type: string
                    enum:
                      - pending
                    description: >
                      Always `pending` on creation — the segment is awaiting
                      file upload. Poll `GET /v1/segments/{segment_id}` — it
                      will show `status: pending` until identity resolution
                      completes, then `status: active`.
                  upload_url:
                    type: string
                    description: >
                      30-minute signed upload URL. Upload your file to this URL
                      with the `Content-Type` matching your `file_format`. No
                      `Authorization` header required. Present when
                      `shard_count` is 1 (the default) — absent when sharded,
                      use `upload_urls` instead.
                  upload_urls:
                    type: array
                    items:
                      type: string
                    description: >
                      30-minute signed upload URLs, one per shard,
                      index-ordered. Present only when `shard_count` > 1 was
                      requested — `upload_url` is absent. Every CSV shard must
                      include the same header row.
                  upload_expires_at:
                    type: string
                    format: date-time
                    description: >
                      ISO timestamp when `upload_url`/`upload_urls` expires (30
                      minutes from creation).
                  expires_at:
                    type: string
                    description: >
                      ISO date — 90-day hard expiry for this segment. After this
                      date the segment and associated files are permanently
                      deleted.
                  file_format:
                    type: string
                    enum:
                      - csv
                      - avro
                      - json
                      - jsonl
                    description: |
                      Confirmed file format for this job.
                  compression:
                    type: string
                    enum:
                      - none
                      - gzip
                    description: Confirmed input artifact compression.
              examples:
                csv_job:
                  summary: CSV job with audience (default)
                  value:
                    match_id: seg_csv123
                    segment_id: seg_csv123
                    audience_id: aud_csv123
                    name: Q2 2026 CRM Refresh
                    status: pending
                    upload_url: https://storage.googleapis.com/cf-uploads/enrichment-uploads/org_x/seg_csv123/input.csv?X-Goog-Signature=...
                    upload_expires_at: 2026-06-22T19:30:00.000Z
                    expires_at: 2026-09-27
                    file_format: csv
                    compression: none
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          description: >
            `IDEMPOTENCY_KEY_CONFLICT` — the `Idempotency-Key` header was
            already used with a different request body.
            `IDEMPOTENCY_REQUEST_IN_PROGRESS` — another request with this key is
            in flight; retry after the seconds in `Retry-After`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - code
                  - message
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - IDEMPOTENCY_KEY_CONFLICT
                      - IDEMPOTENCY_REQUEST_IN_PROGRESS
                  message:
                    type: string
    get:
      summary: List file match jobs
      operationId: listFileMatchJobs
      tags:
        - Enrichment
      description: >
        List file match jobs for the org, newest first. Cursor-paginated.
        Optional `status` filter. Job history persists even after the underlying
        segment or its linked audience is deleted from Library — deleted jobs
        are not excluded from this list.

        Requires 'discovery' scope.
      security:
        - bearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 30
            maximum: 100
        - in: query
          name: before
          schema:
            type: string
            format: date-time
          description: Exclusive upper bound cursor on created_at (ISO 8601)
        - in: query
          name: status
          schema:
            type: string
            enum:
              - pending
              - active
              - failed
              - archived
              - expired
          description: Filter by segment status
      responses:
        "200":
          description: Paginated list of file match jobs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileMatchJobList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/match/{id}/deliveries:
    description: >
      Convenience wrapper around POST /v1/audiences/{id}/deliveries for match
      jobs. The {id} is the match_id (segment document ID) returned by POST
      /v1/match/file — the server resolves the audience that wraps the segment
      and forwards the delivery. Always delivers to the download destination.
    get:
      summary: List deliveries for a file match job
      operationId: listFileMatchDeliveries
      tags:
        - Deliveries
      description: >
        Lists deliveries for the audience linked to the given match (segment)
        ID. Resolves the audience that references the segment, then proxies to
        `GET /v1/audiences/{id}/deliveries` and returns its response unchanged.
              Requires 'discovery' scope.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: >
            The `match_id` (segment document ID) returned by `POST
            /v1/match/file`.
      responses:
        "200":
          description: List of deliveries for the linked audience.
          content:
            application/json:
              schema:
                type: object
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: "#/components/schemas/DeliveryObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      summary: Deliver a file match job
      operationId: deliverFileMatch
      tags:
        - Deliveries
      description: >
        **Convenience wrapper around `POST /v1/audiences/{id}/deliveries` for
        file-based match jobs.** Always delivers to the `download` destination —
        the enriched file is exported to a time-limited signed URL. Omits
        DSP-only destinations (LiveRamp, Narrative) and their associated
        parameters.


        **Workflow (after POST /v1/match/file):**

        1. Upload your file to the `upload_url` returned by `POST
        /v1/match/file`. This
           upload is what triggers identity resolution to begin — not this endpoint.

        2. *(Recommended)* `POST /v1/match/{segment_id}/analyze` — preview
        column mapping.
           Check `unmapped_columns` and `confidence` values. If mappings look wrong, rename
           columns in your file and re-upload before triggering delivery.

        3. Poll `GET /v1/match/file/{match_id}` until `matching_status` is
        `completed` (or use a
           `segment.ready` webhook). Calling this endpoint before resolution finishes returns
           `422 AUDIENCE_PENDING`.

        4. **`POST /v1/match/{match_id}/deliveries`** (this endpoint) — export
        the already-
           resolved match to a signed download URL. Optionally supply `template_id` or
           `field_list` to choose which enriched attributes to include in the output — if
           neither is given, the Standard IAG attribute set is used.

        5. Poll **`GET /v1/match/{match_id}/deliveries`** for status — it takes
        the match id
           directly, needs no audience id, and re-signs `download_urls` on every read. (The
           audience-scoped `GET /v1/audiences/{audience_id}/deliveries/{delivery_id}` also
           works but requires the audience id, which this wrapper is designed to let you avoid.)
           When `status: completed`, `download_urls` (array of signed URLs) is returned.


        All billing, dedup, reservation, and usage-outbox logic is handled
        identically to `POST /v1/audiences/{id}/deliveries`. Re-running the
        exact same (match_id × version × template) is always free.
              Requires 'purchase' scope.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: |
            The `match_id` returned by `POST /v1/match/file`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                include_unmatched:
                  type: boolean
                  default: true
                  description: >
                    Include rows that did not resolve to a real graph match in
                    the output file — these appear with null enrichment
                    attributes and, in the exported `iag_person_id` column, a
                    best-effort id deterministically derived from the row's own
                    identity signals (or `null` if none qualified) rather than a
                    real platform match. Set to `false` to receive only matched
                    records. Defaults to `true`. Every exported row (matched or
                    not) also carries a `row_id` column matching your original
                    upload, for correlating a delivered row back to its source
                    input row regardless of match status. Matched rows
                    additionally carry `match_level` (`I`/`H`/`A`/`S`/`D`),
                    `match_type` (e.g. `graph_name_email_match`,
                    `vector_name_address_match`, `spatial_match`), and
                    `match_confidence` (0–1) — all `null` (not derived) on
                    unmatched rows.
                template_id:
                  type:
                    - string
                    - "null"
                  description: >
                    Enrichment template ID. Only `standard_iag` is valid (see
                    `templates[]` in `GET /v1/catalog/fields`). Mutually
                    exclusive with `field_list`. If neither is provided, the
                    Standard IAG attribute set (all attributes with
                    `product_usage` containing `audience`) is used.
                field_list:
                  type:
                    - array
                    - "null"
                  items:
                    type: string
                  description: >
                    Explicit list of attribute names to append — every attribute
                    must be a valid audience attribute. Use `GET
                    /v1/catalog/fields` to browse available attributes. Mutually
                    exclusive with `template_id`. If neither is provided, the
                    Standard IAG attribute set is used. Individual-level
                    attributes come back `null` for a row whose `match_level`
                    doesn't qualify for individual-level data — this is
                    expected, not an error. If `iag_household_id` is requested
                    and available for a row, it is exported in the same
                    org-scoped, non-reversible format as `iag_person_id`.
                webhook_url:
                  type: string
                  format: uri
                  description: >
                    Optional HTTPS URL to receive `delivery.completed` or
                    `delivery.failed` events for this delivery. Overrides the
                    org-level webhook URL for this request only; if omitted, the
                    org-configured URL is used as the fallback. Every dispatch
                    is signed regardless of which URL is used — see the Webhooks
                    tag for the envelope shape and signing scheme.
                output_format:
                  type:
                    - string
                    - "null"
                  enum:
                    - csv
                    - avro
                    - json
                    - jsonl
                  description: >
                    Output file format. Omit to auto-detect from the uploaded
                    file's format (avro → avro, csv → csv, json → json, jsonl →
                    jsonl). Avro exports preserve native column types including
                    `REPEATED`/array attributes. CSV is the default when format
                    cannot be determined.
                output_compression:
                  type: string
                  enum:
                    - none
                    - gzip
                  default: none
                  description: >
                    Explicit output artifact compression, independent of the
                    uploaded file. gzip is supported for csv/json/jsonl. Avro
                    uses native DEFLATE and rejects outer gzip.
            examples:
              with_template:
                summary: Deliver using the Standard IAG template
                value:
                  template_id: standard_iag
              with_fields:
                summary: Deliver a custom attribute set, matched records only
                value:
                  field_list:
                    - age
                    - gender
                    - estimated_household_income
                  include_unmatched: false
              with_webhook:
                summary: Deliver with webhook notification
                value:
                  template_id: standard_iag
                  webhook_url: https://hooks.example.com/match-complete
      responses:
        "201":
          description: >
            Delivery initiated — or a free re-delivery if this (match_id ×
            version × template) was already completed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeliveryObject"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/deliveries:
    get:
      summary: List org-wide deliveries
      operationId: listOrgDeliveries
      description: >
        Returns a paginated list of all deliveries across all audiences in the
        org, ordered by creation date descending. Requires 'discovery' scope.
      tags:
        - Deliveries
      security:
        - bearerAuth: []
      parameters:
        - name: audience_id
          in: query
          schema:
            type: string
          description: |
            Filter by specific audience ID.
        - name: status
          in: query
          schema:
            type: string
            enum:
              - processing
              - completed
              - failed
          description: |
            Filter by delivery status.
        - name: destination
          in: query
          schema:
            type: string
            enum:
              - download
              - liveramp
              - narrative
          description: |
            Filter by delivery destination.
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
            maximum: 100
          description: |
            Number of results per page.
        - name: cursor
          in: query
          schema:
            type: string
          description: |
            Pagination cursor from previous response next_cursor attribute.
      responses:
        "200":
          description: |
            Paginated delivery list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: "#/components/schemas/DeliveryObject"
                  total:
                    type: integer
                  next_cursor:
                    type:
                      - string
                      - "null"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/workflows/{workflow_run_id}:
    get:
      summary: Get workflow status
      operationId: getWorkflowStatus
      description: >
        Returns the current status of a similarity or propensity audience
        workflow run. Poll this endpoint after creating a similarity or
        propensity audience to track progress. Use `wait_for_workflow` (MCP) for
        a blocking poll, or call this endpoint directly for non-blocking status
        checks.
              Requires 'discovery' scope.
      tags:
        - Workflows
      security:
        - bearerAuth: []
      parameters:
        - name: workflow_run_id
          in: path
          required: true
          schema:
            type: string
          description: |
            The workflow run ID returned in the POST /v1/audiences response.
      responses:
        "200":
          description: |
            Workflow status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  workflow_run_id:
                    type: string
                  status:
                    type: string
                    enum:
                      - running
                      - paused
                      - completed
                      - failed
                    description: >
                      running = steps executing normally; paused = waiting for
                      HITL gate response (only when hitl=true); completed =
                      workflow finished, audience is active; failed = workflow
                      encountered an unrecoverable error.
                  current_step:
                    type: integer
                    description: |
                      Current step index (1-based).
                  step_count:
                    type: integer
                    description: |
                      Total number of steps in the workflow.
                  paused_gate:
                    type:
                      - object
                      - "null"
                    description: >
                      Present only when status is paused and hitl=true. Contains
                      gate info for user response.
                    properties:
                      gate_id:
                        type: string
                        description: >
                          Opaque gate identifier — pass to POST
                          /v1/workflows/{workflow_run_id}/resume.
                      message:
                        type: string
                        description: |
                          Human-readable message describing the decision needed.
                      options:
                        type: array
                        items:
                          type: string
                        description: >
                          Valid response values — pick one and pass as
                          `response` to the resume endpoint.
              examples:
                running:
                  summary: Workflow in progress
                  value:
                    workflow_run_id: wf_abc123
                    status: running
                    current_step: 2
                    step_count: 5
                    paused_gate: null
                paused:
                  summary: Workflow paused at HITL gate
                  value:
                    workflow_run_id: wf_abc123
                    status: paused
                    current_step: 3
                    step_count: 5
                    paused_gate:
                      gate_id: gate_review_icp
                      message: Review the generated ICP profile and confirm it matches your target
                        audience.
                      options:
                        - approve
                        - reject
                        - refine
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/workflows/{workflow_run_id}/resume:
    post:
      summary: Respond to a HITL workflow gate
      operationId: resumeWorkflow
      description: >
        Submit a user response to a paused gate, resuming the workflow from its
        paused state. Only relevant when the audience was created with `hitl:
        true`. The `response` value must match one of the `options` presented in
        the `paused_gate` object from `GET /v1/workflows/{workflow_run_id}`.
              Requires 'purchase' scope.
      tags:
        - Workflows
      security:
        - bearerAuth: []
      parameters:
        - name: workflow_run_id
          in: path
          required: true
          schema:
            type: string
          description: |
            The workflow run ID to resume.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - gate_id
                - response
              properties:
                gate_id:
                  type: string
                  description: |
                    Gate identifier from the paused_gate object.
                response:
                  type: string
                  description: >
                    Selected option — must match one of the values in
                    paused_gate.options.
            examples:
              approve:
                summary: Approve the ICP profile
                value:
                  gate_id: gate_review_icp
                  response: approve
      responses:
        "200":
          description: |
            Gate response accepted, workflow resumed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  workflow_run_id:
                    type: string
                  status:
                    type: string
                    enum:
                      - running
                      - completed
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/account/billing:
    get:
      summary: Get the org's provider-neutral billing projection
      operationId: getAccountBilling
      description: >
        Returns the source-routed billing account, active contract, and rated
        financial projection, platform safety overlays, Stripe payment-method
        readiness, and projection staleness. A parent-billed child receives only
        its own attributed spend and never the agency's pooled balance. Requires
        'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Provider-neutral billing projection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountBillingResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/account/auto-recharge:
    get:
      summary: Get prepaid automatic-recharge state
      operationId: getAccountAutoRecharge
      description: >
        Returns the prepaid balance threshold configuration and a verified
        Stripe hosted-invoice URL only when customer action is required.
        Self-billed prepaid org owners only. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Automatic-recharge state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountAutoRechargeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
    put:
      summary: Configure prepaid automatic recharge
      operationId: updateAccountAutoRecharge
      description: >
        Persists recoverable intent, updates the active billing contract, and
        returns only a freshly re-read threshold projection. Enabling requires a
        ready Stripe payment method and billing address. Self-billed prepaid org
        owners only. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutoRechargeUpdateRequest"
      responses:
        "200":
          description: Updated automatic-recharge state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountAutoRechargeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/credits:
    get:
      summary: List active paid commits and promotional credits
      operationId: getAccountCredits
      description: >
        Reads active balance detail, including applicability and expiration.
        Parent-billed children cannot inspect their agency's pooled balances.
        Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Active balance detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountCreditsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/invoices:
    get:
      summary: List invoices with hosted collection links
      operationId: getAccountInvoices
      description: >
        Returns provider-rated commercial invoices and explicitly labelled
        administrator-created exceptional invoices, with collection status and
        hosted links. Parent-billed children cannot inspect agency invoices.
        Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Invoice history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountInvoicesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/payment-method/setup:
    post:
      summary: Start Stripe payment-method onboarding for the billing account
      operationId: createAccountPaymentMethodSetup
      description: >
        Creates a Stripe Checkout setup session for the Stripe customer linked
        to the source-routed billing account. It does not create or mutate a
        Stripe Subscription. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - success_url
                - cancel_url
              properties:
                success_url:
                  type: string
                  format: uri
                  maxLength: 2048
                cancel_url:
                  type: string
                  format: uri
                  maxLength: 2048
                tier:
                  type: string
                  enum:
                    - paygo
                    - starter
                    - growth
                    - enterprise
                cadence:
                  type: string
                  enum:
                    - monthly
                    - annual
                commitment_term_months:
                  type: integer
                  enum:
                    - 0
                    - 12
                    - 24
                    - 36
                custom_commitment_amount_cents:
                  type: integer
                  minimum: 1
      responses:
        "200":
          description: Hosted setup session
          content:
            application/json:
              schema:
                type: object
                required:
                  - setup_url
                  - session_id
                  - billing_status
                properties:
                  setup_url:
                    type: string
                    format: uri
                  session_id:
                    type: string
                  billing_status:
                    type: string
                    const: awaiting_payment_method
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "502":
          $ref: "#/components/responses/BadGateway"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/account/topups:
    post:
      summary: Begin an idempotent credit top-up
      operationId: createAccountTopUp
      description: >
        Creates a payment-gated prepaid balance purchase. The durable attempt is
        keyed by request_id; promotional credit is added only after Stripe
        payment is verified. Purchased and promotional value expire after 12
        months. Prepay, self-billed org owners only. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - request_id
                - amount_cents
              properties:
                request_id:
                  type: string
                  minLength: 8
                  maxLength: 160
                amount_cents:
                  type: integer
                  minimum: 10000
                  maximum: 5000000
      responses:
        "202":
          description: Durable top-up attempt accepted
          content:
            application/json:
              schema:
                type: object
                required:
                  - payment_attempt_id
                  - request_id
                  - status
                  - amount_cents
                  - bonus_cents
                  - credit_expires_after_months
                properties:
                  payment_attempt_id:
                    type: string
                  request_id:
                    type: string
                  status:
                    type: string
                    enum:
                      - pending
                      - action_required
                      - paid
                      - failed
                  amount_cents:
                    type: integer
                    minimum: 10000
                  bonus_cents:
                    type: integer
                    minimum: 0
                  credit_expires_after_months:
                    type: integer
                    const: 12
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/topups/{requestId}:
    get:
      summary: Get durable top-up status
      operationId: getAccountTopUp
      description: >
        Returns verified durable state for one top-up request. action_url is a
        Stripe hosted invoice only when customer action is required. Requires
        'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: requestId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Top-up status
          content:
            application/json:
              schema:
                type: object
                required:
                  - payment_attempt_id
                  - request_id
                  - status
                  - amount_cents
                  - bonus_cents
                  - action_url
                  - paid_at
                  - last_error_code
                properties:
                  payment_attempt_id:
                    type: string
                  request_id:
                    type: string
                  status:
                    type: string
                    enum:
                      - pending
                      - action_required
                      - paid
                      - failed
                  amount_cents:
                    type: integer
                  bonus_cents:
                    type: integer
                  action_url:
                    type:
                      - string
                      - "null"
                    format: uri
                  paid_at:
                    type:
                      - string
                      - "null"
                    format: date-time
                  last_error_code:
                    type:
                      - string
                      - "null"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/account/payment-method/portal:
    post:
      summary: Open Stripe payment-method and invoice-history settings
      operationId: createAccountPaymentMethodPortal
      description: >
        Creates a Stripe Billing Portal session limited to payment-method
        management and Stripe collection history. Commercial terms remain
        authoritative in the Infinite Audience billing contract and cannot be
        changed in the portal. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - return_url
              properties:
                return_url:
                  type: string
                  maxLength: 2048
      responses:
        "200":
          description: Stripe portal session
          content:
            application/json:
              schema:
                type: object
                required:
                  - portal_url
                properties:
                  portal_url:
                    type: string
                    format: uri
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/contract/change:
    post:
      summary: Schedule a billing contract change
      operationId: changeAccountContract
      description: >
        Schedules the selected tier and cadence at the next verified contract
        billing boundary. Enterprise custom terms remain platform-admin managed.
        Parent-billed children cannot change the agency contract. Requires
        'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - request_id
                - tier
              properties:
                request_id:
                  type: string
                  minLength: 8
                  maxLength: 160
                tier:
                  type: string
                  enum:
                    - starter
                    - growth
                    - enterprise
                cadence:
                  type: string
                  enum:
                    - monthly
                    - annual
      responses:
        "202":
          description: Contract replacement scheduled
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - effective_at
                  - scheduled_change
                properties:
                  status:
                    type: string
                    const: scheduled
                  effective_at:
                    type: string
                    format: date-time
                  scheduled_change:
                    $ref: "#/components/schemas/BillingScheduledContractChange"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/contract/cancel:
    post:
      summary: Schedule contract cancellation
      operationId: cancelAccountContract
      description: >
        Schedules cancellation at the later of the current billing-period end or
        contractual commitment end. An organisation owner is required. Requires
        'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - request_id
              properties:
                request_id:
                  type: string
                  minLength: 8
                  maxLength: 160
      responses:
        "202":
          description: Cancellation scheduled
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - effective_at
                  - scheduled_change
                properties:
                  status:
                    type: string
                    const: scheduled
                  effective_at:
                    type: string
                    format: date-time
                  scheduled_change:
                    $ref: "#/components/schemas/BillingScheduledContractChange"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/contract/cancellation/revert:
    post:
      summary: Revert a scheduled cancellation
      operationId: revertAccountContractCancellation
      description: >
        Restores the billing contract end date before cancellation becomes
        effective. An organisation owner is required. Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - request_id
              properties:
                request_id:
                  type: string
                  minLength: 8
                  maxLength: 160
      responses:
        "200":
          description: Cancellation reverted
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - scheduled_change
                properties:
                  status:
                    type: string
                    const: reverted
                  scheduled_change:
                    type: "null"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "502":
          $ref: "#/components/responses/BadGateway"
  /v1/account/usage:
    get:
      summary: Get aggregated usage metrics
      operationId: getAccountUsage
      description: >
        Returns aggregated usage metrics for your organisation: segment and
        audience counts, deliveries created in the last 30 days, rated
        current-period spend, provider projection metadata, and compute ceiling
        usage as a percentage. Billing-model-agnostic — same shape for prepay
        and postpay/agency orgs.
              Requires 'account' scope.
      tags:
        - Account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Usage metrics.
          content:
            application/json:
              schema:
                type: object
                required:
                  - segment_count
                  - audience_count
                  - deliveries_30d
                  - spend_30d_cents
                  - compute_usage_pct
                properties:
                  segment_count:
                    type: integer
                  audience_count:
                    type: integer
                  deliveries_30d:
                    type: integer
                    description: Deliveries created in the last 30 days.
                  spend_30d_cents:
                    type: integer
                  compute_usage_pct:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/api-keys:
    get:
      summary: List API keys for the caller's org
      operationId: listSettingsApiKeys
      description: >
        Returns all API keys for the caller's org, ordered by creation date
        descending. Key values are never included in list responses.
              Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            List of API keys.
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        scopes:
                          type: array
                          items:
                            type: string
                        active:
                          type: boolean
                        created_at:
                          type: string
                          format: date-time
                        last_used:
                          type:
                            - string
                            - "null"
                          format: date-time
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    post:
      summary: Create a self-service API key
      operationId: createSettingsApiKey
      description: >
        Any authenticated user can create an API key for their own org. Allowed
        scopes: discovery, purchase, account. The plaintext key is returned once
        — it is never stored. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: |
                    Human-readable label for the key.
                scopes:
                  type: array
                  items:
                    type: string
                    enum:
                      - discovery
                      - purchase
                      - account
                  default:
                    - discovery
      responses:
        "201":
          description: |
            API key created — raw_key shown once only.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  raw_key:
                    type: string
                    description: |
                      Plaintext key — copy immediately, not stored.
                  name:
                    type: string
                  scopes:
                    type: array
                    items:
                      type: string
                  created_at:
                    type: string
                    format: date-time
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/api-keys/{id}:
    delete:
      summary: Revoke a self-service API key
      operationId: revokeSettingsApiKey
      description: >
        Immediately deactivates the key. Key must belong to the caller's org.
        Org owner/admin can revoke any key in the org; other members can only
        revoke their own keys (403 SCOPE_REQUIRED otherwise). Requires 'account'
        scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: |
            Key revoked.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/settings/webhook:
    get:
      summary: Get the org's webhook configuration
      operationId: getWebhook
      description: >
        Returns the org's single, implicit webhook recipient — a URL with no
        per-event filtering, always subscribed to all four notifiable event
        types. For per-event-filtered, independently revocable subscriptions
        (e.g. one per Zap), see the Triggers tag instead. The signing secret is
        masked to its last 4 characters. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Current webhook config (or null if not configured).
          content:
            application/json:
              schema:
                type: object
                required:
                  - webhook_config
                properties:
                  webhook_config:
                    type:
                      - object
                      - "null"
                    required:
                      - url
                      - secret
                    properties:
                      url:
                        type:
                          - string
                          - "null"
                        format: uri
                      secret:
                        type:
                          - string
                          - "null"
                        description: Masked — last 4 chars only, or null if unset
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    put:
      summary: Configure the org's outbound webhook
      operationId: updateWebhook
      description: >
        Sets the org-level webhook URL. When configured, the platform fires POST
        requests to this URL when async operations complete — audience builds
        (enrichment, propensity, similarity) and file delivery exports. Events
        fired: segment.ready, segment.failed, delivery.completed,
        delivery.failed. The signing secret is platform-generated, not a request
        field — see the Webhooks tag for the envelope shape and signing scheme,
        and `POST /v1/settings/webhook/secret` to rotate it. Requires 'account'
        scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
                  description: |
                    HTTPS endpoint URL to receive webhook POST requests.
      responses:
        "200":
          description: |
            Webhook configured.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
                  secret:
                    type: string
                    description: >
                      Plaintext signing secret — present ONLY the first time a
                      webhook is configured for this org (the platform just
                      generated it). A later call that only changes the URL
                      omits this field, since the existing secret is preserved
                      unchanged. Store it now; it is never retrievable again —
                      use `POST /v1/settings/webhook/secret` to rotate if lost.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/webhook/secret:
    post:
      summary: Rotate the org's webhook signing secret
      operationId: rotateWebhookSecret
      description: >
        Rotates the org-level webhook signing secret and returns the new
        plaintext once. Requires a webhook URL to already be configured. No
        dual-secret rollover window is needed: dispatch re-reads the signing
        secret live on every attempt, so a delivery that fails verification
        under the old secret simply re-signs with the new one on its next retry
        and self-heals once your receiver is updated. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Secret rotated.
          content:
            application/json:
              schema:
                type: object
                required:
                  - secret
                properties:
                  secret:
                    type: string
                    description: Plaintext signing secret. Store it now; it is never retrievable
                      again.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/notifications:
    get:
      summary: Get the caller's own notification preferences
      operationId: getNotificationPreferences
      description: >
        Returns the caller's per-event-type email notification preferences — the
        user-scoped equivalent of the org-scoped webhook config, covering the
        same four event types. Unset keys default to `false` (opt-out) — no
        email is sent for any event type until the user explicitly opts in.
        Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Current notification preferences.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationPreferencesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    put:
      summary: Update the caller's own notification preferences
      operationId: updateNotificationPreferences
      description: >
        Updates the caller's own email notification preferences. Only these four
        event types are settable. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - notification_preferences
              properties:
                notification_preferences:
                  $ref: "#/components/schemas/NotificationPreferences"
      responses:
        "200":
          description: |
            Preferences updated.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/delivery-destinations:
    get:
      summary: Get delivery destinations
      operationId: getDeliveryDestinations
      description: >
        Returns the org's current list of configured delivery destinations.
        Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            List of configured destinations.
          content:
            application/json:
              schema:
                type: object
                properties:
                  destinations:
                    type: array
                    items:
                      $ref: "#/components/schemas/DeliveryConfig"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    put:
      summary: Update delivery destinations
      operationId: updateDeliveryDestinations
      description: >
        Replaces the org's configured delivery destinations (GCS, LiveRamp,
        Narrative). Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - destinations
              properties:
                destinations:
                  type: array
                  items:
                    $ref: "#/components/schemas/DeliveryConfig"
      responses:
        "200":
          description: |
            Destinations updated.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/billing-preferences:
    get:
      summary: Get billing preferences
      operationId: getBillingPreferences
      description: >
        Returns platform-owned billing safeguards. The low-balance threshold
        applies to prepay accounts; budget_ceiling applies to postpay accounts.
        Explicit payment-gated top-ups replace the retired local
        automatic-overage-charge preference. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Current billing preferences.
          content:
            application/json:
              schema:
                type: object
                required:
                  - billing_model
                  - low_balance_threshold
                  - budget_ceiling
                properties:
                  billing_model:
                    type: string
                    enum:
                      - prepay
                      - postpay
                  low_balance_threshold:
                    type:
                      - number
                      - "null"
                    description: Prepay only — always null for postpay.
                  budget_ceiling:
                    type:
                      - number
                      - "null"
                    description: Postpay only — always null for prepay. Null also means unset
                      (unlimited) for postpay.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    put:
      summary: Update billing preferences
      operationId: updateBillingPreferences
      description: >
        Updates whichever platform-owned safeguard applies to your org's own
        billing_model. Low-balance thresholds are prepay-only; budget ceilings
        are postpay-only. Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                low_balance_threshold:
                  type:
                    - number
                    - "null"
                  minimum: 0
                  description: Prepay only.
                budget_ceiling:
                  type:
                    - number
                    - "null"
                  minimum: 0
                  description: >
                    Postpay only. Optional hard spend ceiling per billing cycle;
                    null removes the ceiling (unlimited).
      responses:
        "200":
          description: |
            Billing preferences updated.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
        "400":
          description: >
            Invalid request, or the requested field doesn't apply to this org's
            billing model — code PREFERENCE_NOT_APPLICABLE_POSTPAY
            (low_balance_threshold on a postpay org) or
            BUDGET_CEILING_NOT_APPLICABLE_PREPAY (budget_ceiling on a prepay
            org).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                preference_not_applicable_postpay:
                  value:
                    error: Not applicable
                    code: PREFERENCE_NOT_APPLICABLE_POSTPAY
                    message: Low-balance alerts are prepay-only. Use budget_ceiling for postpay
                      billing.
                budget_ceiling_not_applicable_prepay:
                  value:
                    error: Not applicable
                    code: BUDGET_CEILING_NOT_APPLICABLE_PREPAY
                    message: Budget ceiling is a postpay-only setting.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/settings/timezone:
    get:
      summary: Get display timezone
      operationId: getTimezoneSetting
      description: >
        Returns the org's display timezone override — an IANA timezone name
        (e.g. "America/New_York") applied to every timestamp displayed across
        the platform for all org members, or null if unset (each viewer falls
        back to their own browser-detected timezone). Requires 'account' scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      responses:
        "200":
          description: |
            Display timezone setting.
          content:
            application/json:
              schema:
                type: object
                required:
                  - timezone
                properties:
                  timezone:
                    type:
                      - string
                      - "null"
                    example: America/New_York
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    put:
      summary: Set display timezone
      operationId: updateTimezoneSetting
      description: >
        Sets (or clears, with `timezone: null`) the org's display timezone
        override. Must be a valid IANA timezone name or null. Requires 'account'
        scope.
      tags:
        - Settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - timezone
              properties:
                timezone:
                  type:
                    - string
                    - "null"
                  example: America/New_York
      responses:
        "200":
          description: |
            Display timezone updated.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    enum:
                      - true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /mcp:
    servers:
      - url: https://mcp.infiniteaudience.ai
        description: MCP streaming server through the shared public HTTPS load balancer
    post:
      summary: MCP session init / message dispatch
      operationId: mcpPost
      description: >
        Without mcp-session-id: initialises a new MCP session (body must be an
        Initialize request). With mcp-session-id: dispatches a message to the
        existing session.
      tags:
        - MCP
      security:
        - bearerAuth: []
      parameters:
        - name: mcp-session-id
          in: header
          required: false
          schema:
            type: string
          description: Session ID returned from the Initialize response. Omit on first call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: >
                JSON-RPC 2.0 request envelope. On first call (no
                `mcp-session-id`), `method` must be `initialize`. Subsequent
                calls dispatch to any available MCP tool (see the **MCP —
                Available Tools** section below).
              required:
                - jsonrpc
                - method
              properties:
                jsonrpc:
                  type: string
                  enum:
                    - "2.0"
                  description: JSON-RPC version. Always `"2.0"`.
                method:
                  type: string
                  description: >
                    RPC method name. `initialize` on session creation; a tool
                    name (e.g. `tools/call`) on subsequent dispatches.
                id:
                  description: >
                    Client-assigned request ID echoed in the response. Omit for
                    notifications (no response expected).
                  oneOf:
                    - type: string
                    - type: integer
                    - type: "null"
                params:
                  type: object
                  additionalProperties: true
                  description: >
                    Method-specific parameters. For `initialize`, pass protocol
                    version and client capabilities. For `tools/call`, pass
                    `name` (tool name) and `arguments` (tool input object).
            examples:
              initialize:
                summary: Session initialization
                value:
                  jsonrpc: "2.0"
                  id: 1
                  method: initialize
                  params:
                    protocolVersion: 2024-11-05
                    capabilities: {}
                    clientInfo:
                      name: my-client
                      version: "1.0"
              tools_call:
                summary: Invoke a tool
                value:
                  jsonrpc: "2.0"
                  id: 2
                  method: tools/call
                  params:
                    name: list_audiences
                    arguments: {}
      responses:
        "200":
          description: MCP response — JSON-RPC 2.0 envelope.
          content:
            application/json:
              schema:
                type: object
                required:
                  - jsonrpc
                properties:
                  jsonrpc:
                    type: string
                    enum:
                      - "2.0"
                  id:
                    description: Echoes the request id. Null for notifications.
                    oneOf:
                      - type: string
                      - type: integer
                      - type: "null"
                  result:
                    description: Present on success; shape depends on the MCP method called.
                  error:
                    type: object
                    description: Present on failure.
                    required:
                      - code
                      - message
                    properties:
                      code:
                        type: integer
                      message:
                        type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
    get:
      summary: MCP SSE stream
      operationId: mcpGet
      description: SSE stream for an existing MCP session.
      tags:
        - MCP
      security:
        - bearerAuth: []
      parameters:
        - name: mcp-session-id
          in: header
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SSE stream opened — messages arrive as `text/event-stream` events.
          content:
            text/event-stream:
              schema:
                type: string
                description: >
                  Newline-delimited Server-Sent Events. Each `data:` line
                  carries a JSON-RPC 2.0 message (same envelope as the POST
                  response). Clients should parse each event's `data` attribute
                  as JSON.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
    delete:
      summary: MCP session termination
      operationId: mcpDelete
      description: Explicit session termination — cleans up server-side state.
      tags:
        - MCP
      security:
        - bearerAuth: []
      parameters:
        - name: mcp-session-id
          in: header
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Session terminated
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /.well-known/agent.json:
    servers:
      - url: https://a2a.infiniteaudience.ai
        description: A2A discovery server through the shared public HTTPS load balancer
    get:
      summary: A2A agent card
      operationId: a2aAgentCard
      description: >
        Public endpoint. Returns the Infinite Audience agent card describing
        capabilities, skills, and authentication instructions for A2A-compatible
        agent clients.


        **Available skills:** `discovery-count`, `discovery-lookup`,
        `audience-management`, `segment-management`, `catalog`,
        `identity-resolution`, `match-history`, `delivery-history`, `pricing`,
        `data-purchase`, `delivery-status`, `account-info`, `credit-purchase`,
        `subscription-management`, `campaign-management`.


        The `match-history` skill covers listing and retrieving past micro-batch
        runs and file-based identity resolution jobs. Requires `discovery` scope
        only.
      tags:
        - A2A
      security: []
      responses:
        "200":
          description: Infinite Audience A2A agent card.
          content:
            application/json:
              schema:
                type: object
                required:
                  - name
                  - description
                  - url
                  - version
                  - authentication
                  - skills
                properties:
                  name:
                    type: string
                  description:
                    type: string
                  url:
                    type: string
                    format: uri
                    description: Base URL for A2A task submission.
                  version:
                    type: string
                  authentication:
                    type: object
                    required:
                      - schemes
                    properties:
                      schemes:
                        type: array
                        items:
                          type: string
                        example:
                          - bearer
                      credentials_url:
                        type: string
                        format: uri
                        description: Token exchange endpoint (POST /v1/auth/token).
                      instructions:
                        type: string
                  skills:
                    type: array
                    items:
                      type: object
                      required:
                        - id
                        - name
                        - description
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        description:
                          type: string
                        tags:
                          type: array
                          items:
                            type: string
                        examples:
                          type: array
                          items:
                            type: string
        "404":
          $ref: "#/components/responses/NotFound"
  /a2a/tasks:
    servers:
      - url: https://a2a.infiniteaudience.ai
        description: A2A tasks server through the shared public HTTPS load balancer
    post:
      summary: Create an A2A task
      operationId: a2aCreateTask
      description: >
        Submits a new task to the Infinite Audience agent. Returns immediately
        with status "submitted". Poll GET /a2a/tasks/{id} for progress.


        The required scope depends on the requested `skill` (enforced per skill,
        mirroring the REST operations each skill fronts):

        - Requires 'discovery' scope: `discovery-count`, `discovery-lookup`,
          `match-history`, `delivery-history`, `delivery-status`, `catalog`,
          `pricing`.

        - Requires 'purchase' scope: `audience-management`,
        `segment-management`,
          `data-purchase`, `campaign-management`, `identity-resolution`.

        - Requires 'account' scope: `account-info`, `credit-purchase`,
          `subscription-management`.


        Tasks with a missing or unrecognised `skill` are rejected with 400
        before any scope evaluation (fail closed).
      tags:
        - A2A
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - skill
                - messages
              properties:
                skill:
                  type: string
                  enum:
                    - discovery-count
                    - discovery-lookup
                    - match-history
                    - delivery-history
                    - delivery-status
                    - catalog
                    - pricing
                    - audience-management
                    - segment-management
                    - data-purchase
                    - campaign-management
                    - identity-resolution
                    - account-info
                    - credit-purchase
                    - subscription-management
                  description: >
                    Agent-card skill id the task is submitted under — determines
                    the required scope (see operation description).
                    `metadata.skill` is accepted as a fallback for A2A clients
                    that pass hints via metadata, but the top-level attribute is
                    preferred.
                messages:
                  type: array
                  description: >
                    One or more A2A message objects. Each message has a role
                    ("user" or "agent") and a parts array containing content
                    fragments. Only the final user message is acted on; prior
                    messages provide conversation context.
                  items:
                    $ref: "#/components/schemas/A2AMessage"
                metadata:
                  type: object
                  additionalProperties: true
                  description: >
                    Optional caller-defined key/value pairs stored on the task.
                    Set `campaign_id` to provide the task with trusted context
                    from an active campaign in the authenticated organization.
                  properties:
                    campaign_id:
                      type: string
                      minLength: 1
                      description: Active campaign ID used to personalize this task.
            examples:
              audience_count:
                summary: Ask the agent to count an audience
                value:
                  skill: discovery-count
                  messages:
                    - role: user
                      parts:
                        - type: text
                          text: How many people aged 25–34 are in California?
              follow_up:
                summary: Multi-turn — provide additional context
                value:
                  skill: discovery-count
                  messages:
                    - role: user
                      parts:
                        - type: text
                          text: How many people aged 25–34 are in California?
                    - role: agent
                      parts:
                        - type: text
                          text: I can help with that. Should I filter by any interests?
                    - role: user
                      parts:
                        - type: text
                          text: Yes, filter by homeowners only.
      responses:
        "202":
          description: |
            Task accepted — status is "submitted".
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - status
                  - created_at
                properties:
                  id:
                    type: string
                  status:
                    type: string
                    enum:
                      - submitted
                  created_at:
                    type: string
                    format: date-time
              example:
                id: d3e4f5a6-7b8c-9d0e-1f2a-3b4c5d6e7f8a
                status: submitted
                created_at: 2024-01-15T10:30:00Z
        "400":
          description: >
            Missing or unrecognised `skill` (must be one of the skill ids in the
            agent card — GET /.well-known/agent.json), or missing/empty
            `messages` array.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /a2a/tasks/{id}:
    servers:
      - url: https://a2a.infiniteaudience.ai
        description: A2A tasks server through the shared public HTTPS load balancer
    get:
      summary: Get A2A task status
      operationId: a2aGetTask
      description: >
        Poll the status of a submitted A2A task. Possible statuses: submitted,
        working, completed, failed, cancelled.
              Requires 'discovery' scope.
      tags:
        - A2A
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: |
            Task with current status, messages, and result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2ATask"
              examples:
                working:
                  summary: Task still in progress
                  value:
                    id: d3e4f5a6-7b8c-9d0e-1f2a-3b4c5d6e7f8a
                    status: working
                    messages:
                      - role: user
                        parts:
                          - type: text
                            text: How many homeowners aged 25-34 are there in California?
                    created_at: 2024-01-15T10:30:00Z
                    updated_at: 2024-01-15T10:30:05Z
                completed:
                  summary: Task completed with agent response
                  value:
                    id: d3e4f5a6-7b8c-9d0e-1f2a-3b4c5d6e7f8a
                    status: completed
                    messages:
                      - role: user
                        parts:
                          - type: text
                            text: How many homeowners aged 25-34 are there in California?
                      - role: agent
                        parts:
                          - type: text
                            text: There are approximately 4.2 million homeowners aged 25–34 in California.
                    result_message:
                      role: agent
                      parts:
                        - type: text
                          text: There are approximately 4.2 million homeowners aged 25–34 in California.
                    created_at: 2024-01-15T10:30:00Z
                    updated_at: 2024-01-15T10:30:12Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /a2a/tasks/{id}/cancel:
    servers:
      - url: https://a2a.infiniteaudience.ai
        description: A2A tasks server through the shared public HTTPS load balancer
    post:
      summary: Cancel an A2A task
      operationId: a2aCancelTask
      description: >
        Request cancellation of a submitted or in-progress task. Requires
        'purchase' scope.
      tags:
        - A2A
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: |
            Task cancelled.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - status
                properties:
                  id:
                    type: string
                    description: The task ID that was cancelled.
                  status:
                    type: string
                    enum:
                      - cancelled
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: |
            Task already in terminal state (completed/failed/cancelled).
  /v1/match/runs:
    get:
      summary: List micro-batch match runs
      operationId: listMatchRuns
      tags:
        - Enrichment
      description: >
        List micro-batch match history for the org, newest first.
        Cursor-paginated — pass `before` (ISO timestamp of the oldest record
        from the previous page) to fetch the next page. Unlimited depth.

        Requires 'discovery' scope.
      security:
        - bearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 30
            maximum: 100
          description: Max records per page
        - in: query
          name: before
          schema:
            type: string
            format: date-time
          description: Exclusive upper bound cursor on created_at (ISO 8601)
      responses:
        "200":
          description: Paginated list of micro-batch match runs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MatchRunList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
  /v1/match/runs/{runId}:
    get:
      summary: Get a micro-batch match run
      operationId: getMatchRun
      tags:
        - Enrichment
      description: |
        Get a single micro-batch match run by ID.
        Requires 'discovery' scope.
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: runId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Micro-batch match run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MatchRun"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/match/file/{matchId}:
    get:
      summary: Get a file match job
      operationId: getFileMatchJob
      tags:
        - Enrichment
      description: >
        Get a single file match job by segment ID. Returns 404 if the segment
        does not exist or is not a matched-subtype segment.

        Requires 'discovery' scope.
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: matchId
          required: true
          schema:
            type: string
        - in: header
          name: X-Integration-Run-Id
          required: false
          schema:
            type: string
            pattern: ^ir_[A-Za-z0-9_-]{40}$
          description: >
            Required for an OAuth partnership to access a private file job; the
            run must belong to the same active OAuth grant.
      responses:
        "200":
          description: File match job details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileMatchJob"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
webhooks:
  segment.ready:
    post:
      operationId: webhookSegmentReady
      summary: Segment build succeeded
      description: >
        POSTed to your webhook URL when an async segment (matched file upload,
        similarity, or propensity) completes successfully. Configure your URL
        via `PUT /v1/settings/webhook`, supply `webhook_url` per-request, or
        subscribe via `POST /v1/webhook-subscriptions`. `subject.id` is the
        segment id; `data.audience_id` is also present when an audience wrapper
        was auto-created alongside the segment (`create_audience: true`, which
        is the default for matched segments). See the Webhooks tag for the
        envelope shape and signing scheme.
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SegmentWebhookReadyPayload"
            example:
              id: evt_1a2b3c4d5e6f7a8b9c0d1e2f
              type: segment.ready
              created_at: 2026-08-26T18:04:12.000Z
              api_version: 2026-08-06
              org_id: org_xyz
              subject:
                kind: segment
                id: seg_abc123
                name: High-Value Purchasers
              data:
                status: completed
                audience_id: aud_abc123
                match_count: 1200000
                record_count: 1500000
              links: {}
      responses:
        "200":
          description: Your endpoint acknowledged the event.
  segment.failed:
    post:
      operationId: webhookSegmentFailed
      summary: Segment build failed
      description: >
        POSTed to your webhook URL when an async segment build fails due to an
        enrichment or workflow error. See the Webhooks tag for the envelope
        shape and signing scheme.
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SegmentWebhookFailedPayload"
            example:
              id: evt_2b3c4d5e6f7a8b9c0d1e2f1a
              type: segment.failed
              created_at: 2026-08-26T18:04:12.000Z
              api_version: 2026-08-06
              org_id: org_xyz
              subject:
                kind: segment
                id: seg_abc123
                name: High-Value Purchasers
              data:
                status: failed
                failure_reason: No usable identity columns found in uploaded file.
              links: {}
      responses:
        "200":
          description: Your endpoint acknowledged the event.
  delivery.completed:
    post:
      operationId: webhookDeliveryCompleted
      summary: Delivery export completed
      description: >
        POSTed to your webhook URL when a delivery export finishes and download
        URLs (or an equivalent destination confirmation) are available.
        `subject.id` is the delivery id. See the Webhooks tag for the envelope
        shape and signing scheme.
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeliveryWebhookCompletedPayload"
            example:
              id: evt_3c4d5e6f7a8b9c0d1e2f1a2b
              type: delivery.completed
              created_at: 2026-08-26T18:04:12.000Z
              api_version: 2026-08-06
              org_id: org_xyz
              subject:
                kind: delivery
                id: del_abc123
                name: null
              data:
                status: completed
                destination: download
                output_record_count: 1200000
                completed_at: 2026-08-26T18:04:10.000Z
                output_format: csv
                output_compression: gzip
                output_bytes: 48213099
                download_urls:
                  - https://storage.googleapis.com/cf-exports/org_xyz/del_abc123/output.csv?X-Goog-Signature=...
              links: {}
      responses:
        "200":
          description: Your endpoint acknowledged the event.
  delivery.failed:
    post:
      operationId: webhookDeliveryFailed
      summary: Delivery export failed
      description: >
        POSTed to your webhook URL when a delivery export fails. See the
        Webhooks tag for the envelope shape and signing scheme.
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeliveryWebhookFailedPayload"
            example:
              id: evt_4d5e6f7a8b9c0d1e2f1a2b3c
              type: delivery.failed
              created_at: 2026-08-26T18:04:12.000Z
              api_version: 2026-08-06
              org_id: org_xyz
              subject:
                kind: delivery
                id: del_abc123
                name: null
              data:
                status: failed
                destination: download
                failure_reason: Insufficient matched records to produce output.
              links: {}
      responses:
        "200":
          description: Your endpoint acknowledged the event.
