swagger: '2.0'
info:
  title: FunnelFlux Billing Service API
  license:
    name: Apache 2.0
    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
  description: |
    FunnelFlux Billing Service API - manages subscriptions, usage, addons, and custom domain lifecycle.

    This service orchestrates across:
    - **Chargebee**: Subscription and billing management
    - **Ledger**: Domain registry (source of truth for ownership)
    - **Cloudflare**: Custom hostname creation/SSL provisioning

    ## Domain Management Architecture

    Domains are stored in Ledger as the source of truth. Cloudflare status is fetched
    on-demand via the `RefreshDomain` endpoint (not on every list call).

    **List Domains**: Returns only Ledger data (fast, no Cloudflare calls)
    **Refresh Domain**: Fetches fresh Cloudflare status + runs redirect check, updates Ledger
    **Create Domain**: Registers in Ledger + creates Cloudflare custom hostname + updates billing
    **Delete Domain**: Removes from Cloudflare + Ledger + updates billing

  version: 1.0.0
  contact:
    name: FunnelFlux Support
    url: 'mailto:support@funnelflux.pro'
    email: support@funnelflux.pro
  termsOfService: 'https://funnelflux.com/pro/terms-and-conditions/'
host: api.funnelflux.pro
basePath: /v1/billing
schemes:
  - https
consumes:
  - application/json
produces:
  - application/json
securityDefinitions:
  Bearer:
    type: apiKey
    name: Authorization
    in: header
    description: 'All requests must pass a valid header of `Authorization: Bearer TOKEN`'
security:
  - Bearer: []
tags:
  - name: Subscription
    description: Subscription and plan management
  - name: Usage
    description: Event usage tracking and reporting
  - name: Add-Ons
    description: Addon quantity and tier management
  - name: Domains
    description: Custom domain management (Ledger + Cloudflare + Billing)
paths:
  /subscription:
    get:
      summary: Get subscription details
      operationId: getSubscription
      tags:
        - Subscription
      description: Returns the subscription details for the authenticated user including plan, features, and billing information.
      responses:
        '200':
          description: Subscription details
          schema:
            $ref: '#/definitions/Subscription'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /usage:
    get:
      summary: Get event usage
      operationId: getEventUsage
      tags:
        - Usage
      description: Returns daily event usage for the authenticated user over the specified period.
      parameters:
        - name: period_days
          in: query
          description: Number of days to look back (30, 60, or 90)
          required: false
          type: integer
          default: 30
          enum: [30, 60, 90]
      responses:
        '200':
          description: Event usage data
          schema:
            $ref: '#/definitions/EventUsageResponse'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /usage/chargebee:
    get:
      summary: Get Chargebee usage records
      operationId: getChargebeeUsage
      tags:
        - Usage
      description: Returns usage records from Chargebee for the authenticated user's subscription.
      responses:
        '200':
          description: Chargebee usage records
          schema:
            $ref: '#/definitions/ChargebeeUsageResponse'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /addons:
    post:
      summary: Update addon quantity (deprecated)
      operationId: updateAddonQuantityDeprecated
      tags:
        - Add-Ons
      deprecated: true
      description: |
        Deprecated compatibility alias for `/addons/quantity`.
        New clients must call `/addons/quantity`.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/UpdateAddonRequest'
      responses:
        '200':
          description: Addon updated successfully
          schema:
            $ref: '#/definitions/UpdateAddonResponse'
        '401':
          description: Not authorized
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /addons/quantity:
    post:
      summary: Update addon quantity
      operationId: updateAddonQuantity
      tags:
        - Add-Ons
      description: Updates the quantity of an addon in the user's subscription.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/UpdateAddonRequest'
      responses:
        '200':
          description: Addon updated successfully
          schema:
            $ref: '#/definitions/UpdateAddonResponse'
        '401':
          description: Not authorized
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /addons/switch:
    post:
      summary: Switch addon tier
      operationId: switchAddonTier
      tags:
        - Add-Ons
      description: |
        Switches between addon tiers (e.g., ai-nodes-basic -> ai-nodes-pro).
        Can be used to add a new addon, switch tiers, or remove an addon entirely.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/SwitchAddonTierRequest'
      responses:
        '200':
          description: Addon tier switched successfully
          schema:
            $ref: '#/definitions/SwitchAddonTierResponse'
        '401':
          description: Not authorized
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /domains:
    get:
      summary: List all domains
      operationId: listDomains
      tags:
        - Domains
      description: |
        Returns all domains for the authenticated user from Ledger.

        **Note**: This is a fast operation that returns Ledger data only.
        Status fields may be empty - use the Refresh endpoint to get fresh
        Cloudflare status for individual domains.
      responses:
        '200':
          description: List of domains
          schema:
            $ref: '#/definitions/ListDomainsResponse'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'
    post:
      summary: Create a new domain
      operationId: createDomain
      tags:
        - Domains
      description: |
        Creates a new custom domain for the authenticated user.

        Orchestrates across:
        1. Ledger: Registers domain ownership
        2. Cloudflare: Creates custom hostname with SSL
        3. Chargebee: Updates addon quantity if beyond free tier

        Returns DNS validation records that the user must add.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/CreateDomainRequest'
      responses:
        '200':
          description: Domain created successfully
          schema:
            $ref: '#/definitions/CreateDomainResponse'
        '400':
          description: Invalid domain or validation error
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        '409':
          description: Domain already exists
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /domains/check:
    post:
      summary: Check domain configuration
      operationId: checkDomain
      tags:
        - Domains
      description: |
        Verifies that a domain is correctly configured to route to FunnelFlux.

        Performs:
        - HTTP request to {domain}/debug/domain-check
        - TLS handshake validation
        - Response verification

        **Note**: Requires authentication for rate limiting and audit logging.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/CheckDomainRequest'
      responses:
        '200':
          description: Domain check result
          schema:
            $ref: '#/definitions/CheckDomainResponse'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /domains/{domain}:
    get:
      summary: Get domain details
      operationId: getDomain
      tags:
        - Domains
      description: |
        Returns detailed information about a specific domain including
        Cloudflare status and DNS validation records.
      parameters:
        - name: domain
          in: path
          description: Domain hostname (e.g., track.example.com)
          required: true
          type: string
      responses:
        '200':
          description: Domain details
          schema:
            $ref: '#/definitions/GetDomainResponse'
        '401':
          description: Not authorized
        '404':
          description: Domain not found
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'
    delete:
      summary: Delete a domain
      operationId: deleteDomain
      tags:
        - Domains
      description: |
        Deletes a custom domain.

        Orchestrates across:
        1. Cloudflare: Removes custom hostname
        2. Ledger: Removes domain registration
        3. Chargebee: Updates addon quantity if applicable
      parameters:
        - name: domain
          in: path
          description: Domain hostname to delete
          required: true
          type: string
      responses:
        '200':
          description: Domain deleted successfully
          schema:
            $ref: '#/definitions/DeleteDomainResponse'
        '401':
          description: Not authorized
        '404':
          description: Domain not found
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /domains/{domain}/refresh:
    post:
      summary: Refresh domain status
      operationId: refreshDomain
      tags:
        - Domains
      description: |
        Fetches fresh status for a domain from Cloudflare and runs a redirect check.

        Use this when users click "Recheck" in the UI to verify their DNS setup.
        This endpoint:
        1. Gets current Cloudflare custom hostname status
        2. Runs HTTP redirect check to verify routing
        3. Returns combined status

        **Note**: This does NOT call Cloudflare's refresh endpoint - it just
        fetches current status. Cloudflare periodically rechecks DNS automatically.
      parameters:
        - name: domain
          in: path
          description: Domain hostname to refresh
          required: true
          type: string
      responses:
        '200':
          description: Refreshed domain status
          schema:
            $ref: '#/definitions/RefreshDomainResponse'
        '401':
          description: Not authorized
        '404':
          description: Domain not found
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/cancel:
    post:
      summary: Cancel subscription
      operationId: cancelSubscription
      tags:
        - Subscription
      description: Schedules a subscription cancellation at end of current billing term. Subscription status changes to "non_renewing" until the term ends.
      responses:
        '200':
          description: Subscription cancellation scheduled
          schema:
            $ref: '#/definitions/CancelSubscriptionResponse'
        '400':
          description: Subscription not in a cancellable state
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        '404':
          description: Subscription not found
          schema:
            $ref: '#/definitions/Error'
        '502':
          description: Upstream billing provider error
          schema:
            $ref: '#/definitions/Error'
        '503':
          description: Billing service unavailable
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/reactivate:
    post:
      summary: Reactivate subscription
      operationId: reactivateSubscription
      tags:
        - Subscription
      description: Reactivates a cancelled subscription in Chargebee. Only works when subscription status is "cancelled".
      responses:
        '200':
          description: Subscription reactivated
          schema:
            $ref: '#/definitions/ReactivateSubscriptionResponse'
        '401':
          description: Not authorized
        '404':
          description: Subscription not found
          schema:
            $ref: '#/definitions/Error'
        '409':
          description: Subscription is already active
          schema:
            $ref: '#/definitions/Error'
        '502':
          description: Upstream billing provider error
          schema:
            $ref: '#/definitions/Error'
        '503':
          description: Billing service unavailable
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /cancellation-feedback:
    post:
      summary: Submit cancellation feedback
      operationId: submitCancellationFeedback
      tags:
        - Subscription
      description: Submits cancellation feedback from the cancel flow. Enriches with subscription data and relays to webhook.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/CancellationFeedbackRequest'
      responses:
        '200':
          description: Feedback submitted
          schema:
            $ref: '#/definitions/CancellationFeedbackResponse'
        '400':
          description: Invalid request (missing or invalid fields)
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/discount-eligibility:
    get:
      summary: Check discount eligibility
      operationId: getDiscountEligibility
      tags:
        - Subscription
      description: Checks if the authenticated user is eligible for a cancel-flow discount coupon.
      responses:
        '200':
          description: Discount eligibility result
          schema:
            $ref: '#/definitions/DiscountEligibilityResponse'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/pause:
    post:
      summary: Pause subscription
      operationId: pauseSubscription
      tags:
        - Subscription
      description: Schedules a subscription pause at end of current term.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/PauseSubscriptionRequest'
      responses:
        '200':
          description: Subscription pause scheduled
          schema:
            $ref: '#/definitions/PauseSubscriptionResponse'
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/resume:
    post:
      summary: Resume subscription
      operationId: resumeSubscription
      tags:
        - Subscription
      description: Resumes a paused subscription immediately. Only works when subscription status is "paused".
      responses:
        '200':
          description: Subscription resumed
          schema:
            $ref: '#/definitions/ResumeSubscriptionResponse'
        '401':
          description: Not authorized
        '404':
          description: Subscription not found
          schema:
            $ref: '#/definitions/Error'
        '409':
          description: Subscription is not paused
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/remove-scheduled-pause:
    post:
      summary: Remove scheduled pause
      operationId: removeScheduledPause
      tags:
        - Subscription
      description: Cancels a scheduled pause that hasn't taken effect yet. Only works when subscription is active with a pending pause.
      responses:
        '200':
          description: Scheduled pause removed
          schema:
            $ref: '#/definitions/RemoveScheduledPauseResponse'
        '400':
          description: No scheduled pause exists
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        '404':
          description: Subscription not found
          schema:
            $ref: '#/definitions/Error'
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/change-plan:
    post:
      summary: Change subscription plan
      operationId: changePlan
      tags:
        - Subscription
      description: Schedules a plan change at end of current term.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/ChangePlanRequest'
      responses:
        '200':
          description: Plan change scheduled
          schema:
            $ref: '#/definitions/ChangePlanResponse'
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/extend-trial:
    post:
      summary: Extend trial period
      operationId: extendTrial
      tags:
        - Subscription
      description: Extends the trial period by the specified number of days (7 or 14).
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/ExtendTrialRequest'
      responses:
        '200':
          description: Trial extended
          schema:
            $ref: '#/definitions/ExtendTrialResponse'
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

  /subscription/apply-coupon:
    post:
      summary: Apply discount coupon
      operationId: applyCoupon
      tags:
        - Subscription
      description: Applies a discount coupon to the subscription.
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/ApplyCouponRequest'
      responses:
        '200':
          description: Coupon applied
          schema:
            $ref: '#/definitions/ApplyCouponResponse'
        '400':
          description: Invalid request
          schema:
            $ref: '#/definitions/Error'
        '401':
          description: Not authorized
        default:
          description: Error response
          schema:
            $ref: '#/definitions/Error'

definitions:
  Error:
    type: object
    properties:
      error:
        type: string
        description: Error code
      error_description:
        type: string
        description: Human-readable error description

  Subscription:
    type: object
    required:
      - owner_id
      - status
      - mrr
      - has_scheduled_changes
      - subscription_items
    properties:
      owner_id:
        type: string
        description: Owner ID
      status:
        type: string
        description: Subscription status (active, cancelled, in_trial, etc.)
        enum:
          - active
          - cancelled
          - in_trial
          - non_renewing
          - paused
      plan:
        $ref: '#/definitions/Plan'
      features:
        $ref: '#/definitions/Features'
      mrr:
        type: integer
        format: int64
        description: Monthly recurring revenue in cents
      next_billing_at:
        type: integer
        format: int64
        description: Unix timestamp of next billing date (null if not scheduled)
      current_term_start:
        type: integer
        format: int64
        description: Unix timestamp of current term start (null if not applicable)
      current_term_end:
        type: integer
        format: int64
        description: Unix timestamp of current term end (null if not applicable)
      trial_start:
        type: integer
        format: int64
        description: Unix timestamp of trial start (null if not in trial)
      trial_end:
        type: integer
        format: int64
        description: Unix timestamp of trial end (null if not in trial)
      cancelled_at:
        type: integer
        format: int64
        description: Unix timestamp when cancelled (null if not cancelled)
      has_scheduled_changes:
        type: boolean
        description: Whether subscription has pending changes
      subscription_items:
        type: array
        items:
          $ref: '#/definitions/SubscriptionItem'
      coupons:
        type: array
        items:
          $ref: '#/definitions/SubscriptionCoupon'
      created_at:
        type: integer
        format: int64
        description: Unix timestamp when subscription was created (null if not available)
      chargebee_customer_id:
        type: string
        description: Chargebee customer ID
      chargebee_subscription_id:
        type: string
        description: Chargebee subscription ID
      pause_date:
        type: integer
        format: int64
        description: Unix timestamp of scheduled or active pause (null if no pause)
      resume_date:
        type: integer
        format: int64
        description: Unix timestamp when subscription will resume from pause (null if no pause)
      email:
        type: string
        description: Chargebee customer email
      name:
        type: string
        description: Chargebee customer full name (first_name + last_name)
      billing_country:
        type: string
        description: Chargebee customer billing address country (ISO 3166-1 alpha-2)

  Plan:
    type: object
    properties:
      id:
        type: string
        description: Plan ID
      name:
        type: string
        description: Plan display name
      tier:
        type: string
        description: Plan tier (basic, pro, ultra)

  Features:
    type: object
    properties:
      features:
        type: object
        additionalProperties:
          $ref: '#/definitions/FeatureInfo'
        description: Map of feature name to feature info

  FeatureInfo:
    type: object
    required:
      - plan
      - level
    properties:
      plan:
        type: string
        description: Plan that provides this feature
      level:
        type: integer
        description: Numeric level for gating

  SubscriptionItem:
    type: object
    properties:
      item_price_id:
        type: string
        description: Item price ID (e.g., pro-plan-USD-Monthly)
      item_type:
        type: string
        description: Type of item (plan or addon)
        enum:
          - plan
          - addon
      quantity:
        type: integer
        description: Quantity of the item
      unit_price:
        type: integer
        format: int64
        description: Price per unit in cents
      trial_end:
        type: integer
        format: int64
        description: Unix timestamp when item trial ends (0 if not in trial)
      is_in_trial:
        type: boolean
        description: Whether the item is currently in trial
      trial_days_remaining:
        type: integer
        format: int32
        description: Days remaining in trial (0 if not in trial)

  SubscriptionCoupon:
    type: object
    properties:
      coupon_id:
        type: string
        description: Coupon ID

  EventUsageResponse:
    type: object
    properties:
      usage:
        type: array
        items:
          $ref: '#/definitions/DailyEventUsage'
      error_code:
        type: string
        description: Error code if request failed
      error_message:
        type: string
        description: Human-readable error description

  DailyEventUsage:
    type: object
    properties:
      usage_date:
        type: string
        description: Date in YYYY-MM-DD format
      event_count:
        type: integer
        format: int64
        description: Number of events on this date

  ChargebeeUsageResponse:
    type: object
    properties:
      usage_records:
        type: array
        items:
          $ref: '#/definitions/ChargebeeUsageRecord'
      error_code:
        type: string
        description: Error code if request failed
      error_message:
        type: string
        description: Human-readable error description

  ChargebeeUsageRecord:
    type: object
    properties:
      id:
        type: string
      subscription_id:
        type: string
      item_price_id:
        type: string
      usage_date:
        type: integer
        format: int64
        description: Unix timestamp
      quantity:
        type: string
        description: Usage quantity as string

  UpdateAddonRequest:
    type: object
    required:
      - addon_id
      - quantity
    properties:
      addon_id:
        type: string
        description: Addon item price ID (e.g., additional-custom-domain)
      quantity:
        type: integer
        description: New quantity for the addon

  UpdateAddonResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'

  SwitchAddonTierRequest:
    type: object
    properties:
      current_addon_id:
        type: string
        description: Current addon to remove (null if adding new)
      new_addon_id:
        type: string
        description: New addon to add (null if removing entirely)
      invoice_immediately:
        type: boolean
        description: Generate prorated invoice immediately (default true)
        default: true

  SwitchAddonTierResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'

  CreateDomainRequest:
    type: object
    required:
      - domain
    properties:
      domain:
        type: string
        description: Domain hostname to add (e.g., track.example.com)

  CreateDomainResponse:
    type: object
    properties:
      success:
        type: boolean
      domain:
        $ref: '#/definitions/DomainInfo'
      billing:
        $ref: '#/definitions/DomainBillingInfo'
      error_code:
        type: string
        description: Error code if creation failed (e.g., DOMAIN_EXISTS, DOMAIN_ALREADY_OWNED)
      error_message:
        type: string
        description: Human-readable error description
      message:
        type: string
        description: Success message

  GetDomainResponse:
    type: object
    properties:
      success:
        type: boolean
      domain:
        $ref: '#/definitions/DomainInfo'
      error_code:
        type: string
        description: Error code if lookup failed (e.g., NOT_FOUND)
      error_message:
        type: string
        description: Human-readable error description

  DeleteDomainResponse:
    type: object
    properties:
      success:
        type: boolean
      message:
        type: string
      billing:
        $ref: '#/definitions/DomainBillingInfo'
      error_code:
        type: string
        description: Error code if deletion failed
      error_message:
        type: string
        description: Human-readable error description

  ListDomainsResponse:
    type: object
    properties:
      success:
        type: boolean
      domains:
        type: array
        items:
          $ref: '#/definitions/DomainSummary'
      billing:
        $ref: '#/definitions/DomainBillingInfo'
      error_code:
        type: string
        description: Error code if listing failed
      error_message:
        type: string
        description: Human-readable error description

  CheckDomainRequest:
    type: object
    required:
      - domain
    properties:
      domain:
        type: string
        description: Domain hostname to check (e.g., track.example.com)

  CheckDomainResponse:
    type: object
    required:
      - success
      - found
      - ssl_valid
    properties:
      success:
        type: boolean
      found:
        type: boolean
        description: Domain routes to FunnelFlux (redirect check passed)
      http_status:
        type: integer
        description: HTTP status from debug endpoint
      ssl_valid:
        type: boolean
        description: SSL certificate is valid (TLS handshake succeeded)
      ssl_error:
        type: string
        description: SSL error message if TLS failed
      response_time_ms:
        type: integer
        format: int64
        description: Response time in milliseconds
      error_message:
        type: string
        description: Error message if check failed
      check_timestamp:
        type: integer
        format: int64
        description: Unix timestamp when check was performed
      cloudflare_status:
        type: string
        description: Cloudflare hostname status (pending, active, moved, deleted)
      cloudflare_ssl_status:
        type: string
        description: Cloudflare SSL status (initializing, pending_validation, active)

  RefreshDomainResponse:
    type: object
    properties:
      success:
        type: boolean
      domain:
        $ref: '#/definitions/DomainInfo'
      check:
        $ref: '#/definitions/CheckDomainResponse'

  DomainInfo:
    type: object
    required:
      - id
      - domain
      - owner
      - status
    properties:
      id:
        type: string
        description: Ledger domain ID
      domain:
        type: string
        description: Domain hostname
      owner:
        type: string
        description: Owner ID
      created_at:
        type: string
        description: ISO timestamp
      cloudflare_id:
        type: string
        description: Cloudflare custom hostname ID
      status:
        type: string
        description: Overall status (pending, active, failed)
        enum:
          - pending
          - active
          - failed
          - moved
          - deleted
      ssl:
        $ref: '#/definitions/DomainSSLInfo'
      ownership_verification:
        $ref: '#/definitions/DomainOwnershipVerification'
      verification_errors:
        type: array
        items:
          type: string
      parsed:
        $ref: '#/definitions/DomainParsedInfo'

  DomainSummary:
    type: object
    required:
      - id
      - domain
      - status
    description: |
      Summary of a domain from Ledger. Status fields may be empty until
      RefreshDomain is called to fetch Cloudflare status.
    properties:
      id:
        type: string
        description: Ledger domain ID
      domain:
        type: string
        description: Domain hostname
      cloudflare_id:
        type: string
        description: Cloudflare custom hostname ID (may be empty)
      status:
        type: string
        description: Overall status (may be empty until refreshed)
      ssl_status:
        type: string
        description: SSL certificate status (may be empty until refreshed)
      verification_errors:
        type: array
        items:
          type: string
      created_at:
        type: string
        description: ISO timestamp

  DomainSSLInfo:
    type: object
    properties:
      status:
        type: string
        description: SSL status (pending_validation, active, etc.)
      txt_name:
        type: string
        description: TXT record name for DCV validation
      txt_value:
        type: string
        description: TXT record value for DCV validation

  DomainOwnershipVerification:
    type: object
    properties:
      type:
        type: string
        description: Verification type (txt)
      name:
        type: string
        description: TXT record name for ownership
      value:
        type: string
        description: TXT record value for ownership

  DomainParsedInfo:
    type: object
    properties:
      subdomain:
        type: string
        description: Subdomain portion (e.g., track) or empty for root
      root_domain:
        type: string
        description: Root domain (e.g., example.com)

  DomainBillingInfo:
    type: object
    properties:
      total_domains:
        type: integer
        description: Total domains for this owner
      free_domains:
        type: integer
        description: Number of free domains (always 2)
      billable_domains:
        type: integer
        description: Domains that count toward billing (total - 2, min 0)
      addon_quantity:
        type: integer
        description: Current addon quantity in Chargebee

  DiscountEligibilityResponse:
    type: object
    properties:
      eligible:
        type: boolean
        description: Whether user can receive a discount
      last_discount_date:
        type: string
        description: ISO 8601 timestamp of last discount (empty string if never)
      cooldown_months:
        type: integer
        format: int32
        description: Cooldown period in months (always 12)
      eligible_after:
        type: string
        description: ISO 8601 date when eligible again (empty string if eligible now)
      current_coupons:
        type: array
        items:
          type: string
        description: Active coupon IDs on subscription
      has_cancel_flow_coupon:
        type: boolean
        description: Whether a cancel-flow coupon is active

  PauseSubscriptionRequest:
    type: object
    required:
      - pause_months
    properties:
      pause_months:
        type: integer
        format: int32
        description: Number of months to pause (1, 2, or 3)
        enum: [1, 2, 3]

  PauseSubscriptionResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      pause_date:
        type: string
        description: ISO 8601 timestamp when pause begins
      resume_date:
        type: string
        description: ISO 8601 timestamp when subscription resumes
      message:
        type: string

  ChangePlanRequest:
    type: object
    required:
      - new_plan_id
    properties:
      new_plan_id:
        type: string
        description: Target plan item_price_id

  ChangePlanResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      effective_date:
        type: string
        description: ISO 8601 timestamp when change takes effect
      message:
        type: string

  ExtendTrialRequest:
    type: object
    required:
      - extension_days
    properties:
      extension_days:
        type: integer
        format: int32
        description: Days to extend trial (7 or 14)
        enum: [7, 14]

  ExtendTrialResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      new_trial_end:
        type: string
        description: ISO 8601 timestamp of new trial end
      extended_by_days:
        type: integer
        format: int32
        description: Actual days extended
      message:
        type: string

  ApplyCouponRequest:
    type: object
    required:
      - coupon_id
    properties:
      coupon_id:
        type: string
        description: Chargebee coupon ID (e.g., cancel-flow-30pct-3mo)

  ApplyCouponResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      coupon_applied:
        type: string
        description: Coupon ID that was applied
      message:
        type: string

  CancelSubscriptionResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      message:
        type: string

  ReactivateSubscriptionResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      message:
        type: string

  ResumeSubscriptionResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      message:
        type: string

  RemoveScheduledPauseResponse:
    type: object
    properties:
      success:
        type: boolean
      subscription:
        $ref: '#/definitions/Subscription'
      message:
        type: string

  CancellationFeedbackRequest:
    type: object
    required:
      - event_type
      - flow_type
      - language
    properties:
      event_type:
        type: string
        description: Cancellation flow event type
        enum:
          - started
          - reason_selected
          - offer_selected
          - cancellation_completed
      reason:
        type: string
        description: Legacy alias for canonical English reason label
      reason_code:
        type: string
        description: Canonical reason code for cancellation
        enum:
          - taking_break
          - not_enough_revenue
          - setup_too_difficult
          - switching_competitor
          - campaigns_on_hold
          - missing_integration
          - missing_feature
          - missing_features
          - cant_setup_tracking
          - too_complex
          - too_expensive
          - other
      reason_label:
        type: string
        description: Canonical English reason label. Free translated UI copy should not be sent here.
      flow_type:
        type: string
        description: Type of cancel flow
        enum:
          - active
          - trial
      follow_up_response:
        type: string
        description: Follow-up response text
      feedback:
        type: string
        description: Free-text feedback
      language:
        type: string
        description: User's language code
      outcome:
        type: string
        description: Outcome of the cancel flow
        enum:
          - cancelled
          - paused
          - downgraded_standby
          - downgraded
          - accepted_discount
          - extended_trial
          - trial_extended
          - aborted
          - contacted_support
      offer_shown:
        type: string
        description: Retention offer shown to user
        enum:
          - STANDBY
          - DISCOUNT
          - SUPPORT_SESSION
          - TRIAL_EXTENSION
          - NONE
      offer_selected:
        type: string
        description: Retention action selected by user
        enum:
          - STANDBY
          - DISCOUNT
          - SUPPORT_SESSION
          - TRIAL_EXTENSION
          - PAUSE
          - NONE
      offer_accepted:
        type: boolean
        description: Whether the user accepted the offer
      pause_duration_months:
        type: integer
        format: int32
        description: Pause duration in months when offer_selected is PAUSE
      billing_country:
        type: string
        description: User's billing country code
      plan_id:
        type: string
        description: Optional client-provided plan ID used as analytics context when subscription enrichment cannot provide a non-empty server value; backend subscription value wins when available
      subscription_status:
        type: string
        description: Optional client-provided subscription status used as analytics context when subscription enrichment cannot provide a non-empty server value; backend subscription value wins when available
      created_at_client:
        type: string
        format: date-time
        description: Optional client timestamp for late-arrival ordering. Server created_at remains authoritative.

  CancellationFeedbackResponse:
    type: object
    properties:
      success:
        type: boolean
