# Generated by apps/api/scripts/generate-openapi.ts — DO NOT EDIT BY HAND.
# Source of truth: apps/api/src/openapi/schemas.ts (buildOpenApiDocument).
# Regenerate: pnpm --filter @fyralynx/api generate:openapi
openapi: 3.1.0
info:
  title: Fyralynx API
  version: v1
  description: |-
    Global affiliate routing engine REST API.

    ## Authentication
    All endpoints (except GET /v1/health) require a Bearer API key:
    ```
    Authorization: Bearer fyr_live_...
    ```

    ## Rate Limits
    - **Publisher**: 60 requests / minute
    - **Enterprise**: 600 requests / minute

    Rate-limit headers are returned on every response:
    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`

    ## Error format
    All errors return `{ success: false, error: { code, message, details?, requestId? } }`.
    Switch on `error.code` (not HTTP status) to handle errors programmatically.

    Blueprint §25 — Public REST API
  contact:
    name: Fyralynx Support
    url: https://fyralynx.com/support
  license:
    name: Proprietary
servers:
  - url: https://api.fyralynx.com/v1
    description: Production
  - url: https://api-staging.fyralynx.com/v1
    description: Staging
security:
  - BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: "API key issued from the Fyralynx dashboard. Format: `fyr_live_...`"
  schemas:
    NetworkId:
      type: string
      title: NetworkId
      description: Fyralynx affiliate network identifier.
      enum:
        - CJ
        - Awin
        - Impact
        - Admitad
        - Cuelinks
        - Rakuten
        - InvolveAsia
        - AmazonAssociates
        - FlipkartAffiliate
        - NoonAffiliate
        - JumiaAffiliate
        - TakealotAffiliate
      example: Impact
    RoutingPresetId:
      type: string
      title: RoutingPresetId
      description: |-
        Routing preset controlling how EPC scores are weighted and
        how redirects are handled.

        - `default`: Balanced EPC, conservative risk controls. Best for blogs, newsletters.
        - `link_in_bio`: Mobile-first, fast geo-fallback. Best for Instagram, TikTok bios.
        - `long_form`: Aggressive EPC, deep-link priority. Best for YouTube, long-form posts.
      enum:
        - default
        - link_in_bio
        - long_form
      example: default
    ProductCategory:
      type: string
      title: ProductCategory
      description: Product category used to apply the category EPC multiplier in routing.
      enum:
        - electronics
        - computers
        - mobile
        - fashion
        - accessories
        - beauty
        - home
        - furniture
        - books
        - sports
        - toys
        - grocery
        - automotive
        - health
        - garden
        - tools
        - handmade
        - collectibles
        - other
      example: electronics
    IsoAlpha2:
      type: string
      title: IsoAlpha2
      description: ISO 3166-1 alpha-2 country code. Must be a country supported by Fyralynx.
      minLength: 2
      maxLength: 2
      pattern: ^[A-Z]{2}$
      example: IN
    DeviceType:
      type: string
      title: DeviceType
      description: Visitor device type used in routing decisions.
      enum:
        - desktop
        - mobile
        - tablet
      example: mobile
    AnalyticsDate:
      type: string
      format: date
      description: ISO 8601 date string (YYYY-MM-DD). Analytics uses day-level granularity only.
      pattern: ^\d{4}-\d{2}-\d{2}$
      example: 2026-01-15
    ApiErrorCode:
      type: string
      title: ApiErrorCode
      description: Machine-readable error code. Switch on this to handle errors programmatically.
      enum:
        - UNAUTHORIZED
        - INVALID_API_KEY
        - PLAN_UPGRADE_REQUIRED
        - FREE_TIER_LIMIT_REACHED
        - RATE_LIMIT_EXCEEDED
        - VALIDATION_ERROR
        - INVALID_URL
        - UNSUPPORTED_MERCHANT
        - BULK_LIMIT_EXCEEDED
        - LINK_NOT_FOUND
        - MERCHANT_NOT_FOUND
        - CREATOR_NOT_FOUND
        - GROUP_NOT_FOUND
        - NO_ROUTING_CANDIDATES
        - NO_ACTIVE_NETWORK_ACCOUNT
        - NETWORK_OVERRIDE_UNAVAILABLE
        - ROUTING_FALLBACK
        - INTERNAL_ERROR
        - KV_ERROR
        - FX_DATA_UNAVAILABLE
        - PAYMENT_PROVIDER_ERROR
    ApiError:
      type: object
      title: ApiError
      description: Structured error returned in all error responses.
      required:
        - code
        - message
      properties:
        code:
          type: string
          title: ApiErrorCode
          description: Machine-readable error code. Switch on this to handle errors programmatically.
          enum:
            - UNAUTHORIZED
            - INVALID_API_KEY
            - PLAN_UPGRADE_REQUIRED
            - FREE_TIER_LIMIT_REACHED
            - RATE_LIMIT_EXCEEDED
            - VALIDATION_ERROR
            - INVALID_URL
            - UNSUPPORTED_MERCHANT
            - BULK_LIMIT_EXCEEDED
            - LINK_NOT_FOUND
            - MERCHANT_NOT_FOUND
            - CREATOR_NOT_FOUND
            - GROUP_NOT_FOUND
            - NO_ROUTING_CANDIDATES
            - NO_ACTIVE_NETWORK_ACCOUNT
            - NETWORK_OVERRIDE_UNAVAILABLE
            - ROUTING_FALLBACK
            - INTERNAL_ERROR
            - KV_ERROR
            - FX_DATA_UNAVAILABLE
            - PAYMENT_PROVIDER_ERROR
        message:
          type: string
          description: Human-readable error message suitable for display or logging.
          example: Link abc123 does not exist.
        details:
          description: Optional structured details. Present for VALIDATION_ERROR — contains field-level errors.
          example:
            originalUrl:
              - A valid URL is required.
        requestId:
          type: string
          description: Request ID for correlating with edge worker logs.
          example: req_01HXYZ
          readOnly: true
    ErrorResponse:
      type: object
      title: ErrorResponse
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          $ref: "#/components/schemas/ApiError"
    UtmOverride:
      type: object
      title: UtmOverride
      description: Per-link UTM parameter overrides. Present values override the active preset's UTM config.
      properties:
        utm_source:
          type: string
          maxLength: 200
          example: newsletter
        utm_medium:
          type: string
          maxLength: 200
          example: email
        utm_campaign:
          type: string
          maxLength: 200
          example: summer-sale-2026
        utm_term:
          type: string
          maxLength: 200
        utm_content:
          type: string
          maxLength: 200
    PassthroughFlag:
      type: object
      title: PassthroughFlag
      description: Passthrough mode configuration. When enabled, no canonicalization, UTM injection, or network routing is applied — the original URL is preserved exactly.
      required:
        - enabled
      properties:
        enabled:
          type: boolean
        reason:
          type: string
          description: Human-readable reason for enabling passthrough (required when enabled = true).
          maxLength: 500
        enabledAt:
          type: string
          format: date-time
          description: ISO 8601 datetime when passthrough was enabled.
          readOnly: true
    Link:
      type: object
      title: Link
      description: A Fyralynx link — the full persisted representation after routing.
      required:
        - linkId
        - title
        - originalUrl
        - canonicalUrl
        - routedUrl
        - fyralynxUrl
        - merchantId
        - region
        - networkId
        - preset
        - passthrough
        - groupId
        - createdAt
        - updatedAt
      properties:
        linkId:
          type: string
          description: Unique link identifier.
          readOnly: true
          example: lnk_01HXABCDEF
        title:
          type: string
          description: Creator-provided or auto-extracted page title.
          minLength: 1
          maxLength: 300
        originalUrl:
          type: string
          format: uri
          description: The original URL as entered by the creator (pre-canonicalization).
          readOnly: true
        canonicalUrl:
          type: string
          format: uri
          description: Canonicalized URL after applying merchant-specific rules. Empty string if passthrough is active.
          readOnly: true
        routedUrl:
          type: string
          format: uri
          description: The final routed URL including affiliate tracking parameters.
          readOnly: true
        fyralynxUrl:
          type: string
          format: uri
          description: The Fyralynx edge redirect URL (go.fyralynx.com/r/{linkId}).
          readOnly: true
          example: https://go.fyralynx.com/r/lnk_01HXABCDEF
        merchantId:
          type: string
          description: ID of the merchant this link routes to.
          readOnly: true
          example: amazon-in
        region:
          type: string
          description: Region code of the chosen storefront.
          readOnly: true
          example: IN
        networkId:
          $ref: "#/components/schemas/NetworkId"
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        category:
          $ref: "#/components/schemas/ProductCategory"
        passthrough:
          $ref: "#/components/schemas/PassthroughFlag"
        networkOverride:
          type: object
          description: Per-link network pin overriding EPC model selection.
          required:
            - networkId
          properties:
            networkId:
              $ref: "#/components/schemas/NetworkId"
            expiresAt:
              type: string
              format: date-time
            reason:
              type: string
              maxLength: 500
        deviceTargets:
          type: object
          description: Device-targeted destination overrides.
          properties:
            mobileUrl:
              type: string
              format: uri
            desktopUrl:
              type: string
              format: uri
        utmOverride:
          $ref: "#/components/schemas/UtmOverride"
        groupId:
          type:
            - string
            - "null"
          description: Group this link belongs to. null = 'All Links'.
        shortcode:
          type: string
          description: Smart shortcode alias accessible as /link/{alias} on the creator's profile.
          example: airpods-pro
        createdAt:
          type: string
          format: date-time
          description: ISO 8601 datetime when this link was created.
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          description: ISO 8601 datetime of last modification.
          readOnly: true
    LinkSummary:
      type: object
      title: LinkSummary
      description: Lightweight link representation for list endpoints. Omits large fields for performance.
      required:
        - linkId
        - title
        - originalUrl
        - routedUrl
        - fyralynxUrl
        - merchantId
        - region
        - networkId
        - preset
        - passthrough
        - groupId
        - createdAt
      properties:
        linkId:
          type: string
          readOnly: true
          example: lnk_01HXABCDEF
        title:
          type: string
        originalUrl:
          type: string
          format: uri
          readOnly: true
        routedUrl:
          type: string
          format: uri
          readOnly: true
        fyralynxUrl:
          type: string
          format: uri
          readOnly: true
        merchantId:
          type: string
          readOnly: true
        region:
          type: string
          readOnly: true
        networkId:
          $ref: "#/components/schemas/NetworkId"
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        category:
          $ref: "#/components/schemas/ProductCategory"
        passthrough:
          $ref: "#/components/schemas/PassthroughFlag"
        groupId:
          type:
            - string
            - "null"
        createdAt:
          type: string
          format: date-time
          readOnly: true
    PaginationMeta:
      type: object
      title: PaginationMeta
      required:
        - page
        - limit
        - total
        - totalPages
        - hasNextPage
        - hasPrevPage
      properties:
        page:
          type: integer
          minimum: 1
        limit:
          type: integer
          minimum: 1
        total:
          type: integer
          minimum: 0
          description: Total number of items matching the query.
        totalPages:
          type: integer
          minimum: 0
        hasNextPage:
          type: boolean
        hasPrevPage:
          type: boolean
    CreateLinkRequest:
      type: object
      title: CreateLinkRequest
      description: Request body for POST /v1/links.
      required:
        - originalUrl
      properties:
        originalUrl:
          type: string
          format: uri
          description: The product URL to route. Must be HTTPS and from a supported merchant domain.
          maxLength: 4096
          example: https://www.amazon.in/dp/B09JQMJHXY
        title:
          type: string
          description: Creator-provided title. Auto-extracted from the page if omitted.
          minLength: 1
          maxLength: 300
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        visitorCountry:
          $ref: "#/components/schemas/IsoAlpha2"
        deviceType:
          $ref: "#/components/schemas/DeviceType"
        category:
          $ref: "#/components/schemas/ProductCategory"
        passthrough:
          type: boolean
          description: "Enable passthrough mode for this link. Default: false."
          default: false
        passthroughReason:
          type: string
          description: Required when passthrough = true. Reason for enabling passthrough mode.
          maxLength: 500
        networkOverride:
          $ref: "#/components/schemas/NetworkId"
          description: Pin a specific network, bypassing EPC model selection.
        utmOverride:
          $ref: "#/components/schemas/UtmOverride"
        groupId:
          type:
            - string
            - "null"
          description: Group ID to assign on creation. null = 'All Links'.
          maxLength: 128
    UpdateLinkRequest:
      type: object
      title: UpdateLinkRequest
      description: Request body for PATCH /v1/links/{id}. Only present fields are updated.
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 300
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        passthrough:
          type: boolean
        passthroughReason:
          type: string
          maxLength: 500
        networkOverride:
          type:
            - string
            - "null"
          description: Set null to remove the network override.
        category:
          type:
            - string
            - "null"
          description: Set null to remove the category override.
        groupId:
          type:
            - string
            - "null"
          description: Set null to move to 'All Links'.
        utmOverride:
          description: Set null to remove all UTM overrides.
          oneOf:
            - $ref: "#/components/schemas/UtmOverride"
            - type: "null"
    RouteClickRequest:
      type: object
      title: RouteClickRequest
      description: Request body for POST /v1/route. Returns a routing decision without persisting a link.
      required:
        - originalUrl
      properties:
        originalUrl:
          type: string
          format: uri
          maxLength: 4096
          example: https://www.amazon.in/dp/B09JQMJHXY
        visitorCountry:
          $ref: "#/components/schemas/IsoAlpha2"
        deviceType:
          $ref: "#/components/schemas/DeviceType"
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        category:
          $ref: "#/components/schemas/ProductCategory"
        includeCrcTrace:
          type: boolean
          description: "If true, includes the full CRC trace in the response. Default: false."
          default: false
    BulkCreateRequest:
      type: object
      title: BulkCreateRequest
      description: Request body for POST /v1/links/bulk (Enterprise only). Max 100 links per request.
      required:
        - links
      properties:
        links:
          type: array
          description: Array of link creation requests. Each item is processed independently. Duplicate originalUrl values within the same request are rejected.
          items:
            $ref: "#/components/schemas/CreateLinkRequest"
          minItems: 1
          maxItems: 100
    CreateLinkResponse:
      type: object
      title: CreateLinkResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - link
          properties:
            link:
              $ref: "#/components/schemas/Link"
    GetLinkResponse:
      type: object
      title: GetLinkResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - link
          properties:
            link:
              $ref: "#/components/schemas/Link"
    ListLinksResponse:
      type: object
      title: ListLinksResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - items
            - pagination
          properties:
            items:
              type: array
              items:
                $ref: "#/components/schemas/LinkSummary"
            pagination:
              $ref: "#/components/schemas/PaginationMeta"
    UpdateLinkResponse:
      type: object
      title: UpdateLinkResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - link
          properties:
            link:
              $ref: "#/components/schemas/Link"
    DeleteLinkResponse:
      type: object
      title: DeleteLinkResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - linkId
            - deleted
          properties:
            linkId:
              type: string
            deleted:
              type: boolean
    BulkCreateFailure:
      type: object
      title: BulkCreateFailure
      required:
        - index
        - originalUrl
        - error
      properties:
        index:
          type: integer
          minimum: 0
          description: Zero-based index in the request `links` array.
        originalUrl:
          type: string
          format: uri
        error:
          $ref: "#/components/schemas/ApiError"
    BulkCreateLinkResponse:
      type: object
      title: BulkCreateLinkResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - created
            - failed
            - totalRequested
            - totalCreated
            - totalFailed
          properties:
            created:
              type: array
              items:
                $ref: "#/components/schemas/Link"
            failed:
              type: array
              items:
                $ref: "#/components/schemas/BulkCreateFailure"
            totalRequested:
              type: integer
              minimum: 0
            totalCreated:
              type: integer
              minimum: 0
            totalFailed:
              type: integer
              minimum: 0
    RouteClickResponse:
      type: object
      title: RouteClickResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - redirectUrl
            - merchantId
            - networkId
            - region
            - normalizedEpcUsd
            - confidence
            - outcome
          properties:
            redirectUrl:
              type: string
              format: uri
              description: The routed affiliate URL.
            merchantId:
              type: string
            networkId:
              $ref: "#/components/schemas/NetworkId"
            region:
              type: string
            normalizedEpcUsd:
              type: number
              description: Normalized EPC in USD at routing time.
            confidence:
              type: number
              minimum: 0
              maximum: 1.1
              description: Routing confidence factor C_r ∈ [0.65, 1.10].
            outcome:
              type: string
              enum:
                - routed
                - fallback
                - passthrough
              description: Routing outcome for this decision.
    MerchantSummary:
      type: object
      title: MerchantSummary
      description: Lightweight merchant summary for list endpoints.
      required:
        - merchantId
        - displayName
        - domains
        - supportedNetworks
        - regionCount
        - dataConfidenceScore
        - lastUpdated
      properties:
        merchantId:
          type: string
          example: amazon-in
        displayName:
          type: string
          example: Amazon India
        domains:
          type: array
          items:
            type: string
          example:
            - amazon.in
        supportedNetworks:
          type: array
          items:
            $ref: "#/components/schemas/NetworkId"
        regionCount:
          type: integer
          minimum: 1
        dataConfidenceScore:
          type: number
          minimum: 0
          maximum: 100
          description: Confidence score for this merchant's EPC data [0, 100].
        lastUpdated:
          type: string
          format: date-time
    ListMerchantsResponse:
      type: object
      title: ListMerchantsResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - merchants
            - total
          properties:
            merchants:
              type: array
              items:
                $ref: "#/components/schemas/MerchantSummary"
            total:
              type: integer
              minimum: 0
    GetMerchantResponse:
      type: object
      title: GetMerchantResponse
      required:
        - success
        - data
      description: Full merchant profile for GET /v1/merchants/{id}.
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - merchant
          properties:
            merchant:
              type: object
              description: Full MerchantProfile from @fyralynx/shared-types.
              additionalProperties: true
    ClickRecord:
      type: object
      title: ClickRecord
      description: A single click record. No PII — country-level granularity only.
      required:
        - recordId
        - linkId
        - merchantId
        - networkId
        - region
        - preset
        - outcome
        - normalizedEpcUsd
        - confidence
        - deepLinkUsed
        - privateDealApplied
        - clickedAt
      properties:
        recordId:
          type: string
          readOnly: true
        linkId:
          type: string
        merchantId:
          type: string
        networkId:
          $ref: "#/components/schemas/NetworkId"
        region:
          type: string
        preset:
          $ref: "#/components/schemas/RoutingPresetId"
        outcome:
          type: string
          enum:
            - routed
            - fallback
            - passthrough
        normalizedEpcUsd:
          type: number
        confidence:
          type: number
          minimum: 0
          maximum: 1.1
        deepLinkUsed:
          type: boolean
        privateDealApplied:
          type: boolean
        clickedAt:
          type: string
          format: date-time
          readOnly: true
    ListClicksResponse:
      type: object
      title: ListClicksResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - items
            - pagination
          properties:
            items:
              type: array
              items:
                $ref: "#/components/schemas/ClickRecord"
            pagination:
              $ref: "#/components/schemas/PaginationMeta"
    GetAnalyticsSummaryResponse:
      type: object
      title: GetAnalyticsSummaryResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - periodStart
            - periodEnd
            - totalClicks
            - routedClicks
            - passthroughClicks
            - fallbackClicks
            - averageConfidence
            - averageNormalizedEpcUsd
            - byMerchant
            - byNetwork
            - byPreset
          properties:
            periodStart:
              type: string
              format: date
            periodEnd:
              type: string
              format: date
            totalClicks:
              type: integer
              minimum: 0
            routedClicks:
              type: integer
              minimum: 0
            passthroughClicks:
              type: integer
              minimum: 0
            fallbackClicks:
              type: integer
              minimum: 0
            averageConfidence:
              type: number
              minimum: 0
              maximum: 1.1
            averageNormalizedEpcUsd:
              type: number
              minimum: 0
            byMerchant:
              type: array
              items:
                type: object
                required:
                  - merchantId
                  - merchantDisplayName
                  - clicks
                  - averageEpcUsd
                properties:
                  merchantId:
                    type: string
                  merchantDisplayName:
                    type: string
                  clicks:
                    type: integer
                  averageEpcUsd:
                    type: number
            byNetwork:
              type: array
              items:
                type: object
                required:
                  - networkId
                  - clicks
                  - averageEpcUsd
                properties:
                  networkId:
                    $ref: "#/components/schemas/NetworkId"
                  clicks:
                    type: integer
                  averageEpcUsd:
                    type: number
            byPreset:
              type: array
              items:
                type: object
                required:
                  - preset
                  - clicks
                properties:
                  preset:
                    $ref: "#/components/schemas/RoutingPresetId"
                  clicks:
                    type: integer
    GetAccountResponse:
      type: object
      title: GetAccountResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - creatorId
            - email
            - displayName
            - plan
            - billingCycle
            - clicksUsedThisPeriod
            - clicksIncludedThisPeriod
            - clicksRemainingThisPeriod
            - periodEnd
            - trialActive
          properties:
            creatorId:
              type: string
              readOnly: true
            email:
              type: string
              format: email
            displayName:
              type: string
            plan:
              type: string
              enum:
                - free
                - creator
                - pro
                - publisher
                - enterprise
              description: Current billing plan ID.
            billingCycle:
              type: string
              enum:
                - monthly
                - annual
            clicksUsedThisPeriod:
              type: integer
              minimum: 0
            clicksIncludedThisPeriod:
              type: integer
              minimum: 0
            clicksRemainingThisPeriod:
              type: integer
              minimum: 0
            periodEnd:
              type: string
              format: date
              description: ISO 8601 date when the current billing period ends.
            trialActive:
              type: boolean
            trialEndAt:
              type: string
              format: date-time
    GetFxRatesResponse:
      type: object
      title: GetFxRatesResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - base
            - rates
            - timestamp
            - isStale
          properties:
            base:
              type: string
              enum:
                - USD
            rates:
              type: object
              description: Currency code → exchange rate (relative to USD).
              additionalProperties:
                type: number
              example:
                INR: 83.21
                EUR: 0.92
                GBP: 0.79
            timestamp:
              type: string
              format: date-time
            isStale:
              type: boolean
              description: True if the FX data is older than 6 hours. Use with caution.
    HealthCheckResponse:
      type: object
      title: HealthCheckResponse
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          required:
            - status
            - version
            - kvLatencyMs
            - timestamp
          properties:
            status:
              type: string
              enum:
                - ok
                - degraded
                - down
              description: Overall health status of the API worker.
            version:
              type: string
              example: v1
            kvLatencyMs:
              type: number
              minimum: 0
            timestamp:
              type: string
              format: date-time
paths:
  /links:
    post:
      operationId: createLink
      summary: Create a routed link
      description: Canonicalizes and routes an affiliate URL. Returns the routed link with a fyralynxUrl.
      tags:
        - Links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLinkRequest"
      responses:
        "201":
          description: Link created successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateLinkResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    get:
      operationId: listLinks
      summary: List links
      description: Paginated, filterable list of all links for the authenticated creator.
      tags:
        - Links
      parameters:
        - name: page
          in: query
          required: false
          description: Page number.
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          required: false
          description: Page size.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: merchantId
          in: query
          required: false
          description: Filter by merchant ID.
          schema:
            type: string
        - name: networkId
          in: query
          required: false
          description: Filter by affiliate network.
          schema:
            $ref: "#/components/schemas/NetworkId"
        - name: preset
          in: query
          required: false
          description: Filter by routing preset.
          schema:
            $ref: "#/components/schemas/RoutingPresetId"
        - name: groupId
          in: query
          required: false
          description: Filter by link group ID.
          schema:
            type: string
        - name: createdAfter
          in: query
          required: false
          description: Filter links created after this datetime.
          schema:
            type: string
            format: date-time
        - name: createdBefore
          in: query
          required: false
          description: Filter links created before this datetime.
          schema:
            type: string
            format: date-time
        - name: search
          in: query
          required: false
          description: Full-text search across title and originalUrl.
          schema:
            type: string
            maxLength: 200
      responses:
        "200":
          description: Paginated list of links.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListLinksResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /links/bulk:
    post:
      operationId: bulkCreateLinks
      summary: Bulk create links (Enterprise)
      description: Create up to 100 routed links in a single request. Enterprise plan only.
      tags:
        - Links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkCreateRequest"
      responses:
        "207":
          description: Partial or full success. Check totalFailed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkCreateLinkResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /links/{id}:
    parameters:
      - name: id
        in: path
        required: true
        description: Link ID.
        schema:
          type: string
    get:
      operationId: getLink
      summary: Get a link
      description: Retrieves the full link representation including CRC trace.
      tags:
        - Links
      responses:
        "200":
          description: Link found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetLinkResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: updateLink
      summary: Update a link
      description: Updates mutable metadata on an existing link. Only present fields are changed.
      tags:
        - Links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateLinkRequest"
      responses:
        "200":
          description: Link updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdateLinkResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteLink
      summary: Delete a link
      description: Soft-deletes a link. The fyralynxUrl will return 404 after deletion.
      tags:
        - Links
      responses:
        "200":
          description: Link deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteLinkResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /route:
    post:
      operationId: routeClick
      summary: Get a routing decision
      description: Returns a routing decision for a URL without persisting a link or counting a click.
      tags:
        - Routing
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RouteClickRequest"
      responses:
        "200":
          description: Routing decision computed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RouteClickResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /merchants:
    get:
      operationId: listMerchants
      summary: List all merchants
      description: Returns all 35 merchants supported by Fyralynx with summary data.
      tags:
        - Merchants
      parameters:
        - name: networkId
          in: query
          required: false
          description: Filter merchants by supported network.
          schema:
            $ref: "#/components/schemas/NetworkId"
        - name: category
          in: query
          required: false
          description: Filter merchants by product category.
          schema:
            $ref: "#/components/schemas/ProductCategory"
      responses:
        "200":
          description: Merchant list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListMerchantsResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /merchants/{id}:
    parameters:
      - name: id
        in: path
        required: true
        description: Merchant ID.
        schema:
          type: string
    get:
      operationId: getMerchant
      summary: Get a merchant profile
      description: Returns the full merchant profile including EPC hints, networks, and region data.
      tags:
        - Merchants
      responses:
        "200":
          description: Merchant profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetMerchantResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /analytics/clicks:
    get:
      operationId: listAnalyticsClicks
      summary: List click records
      description: Paginated click records for the authenticated creator. Date range is required (day-level granularity). Max 365 days (Enterprise) / 90 days (Publisher).
      tags:
        - Analytics
      parameters:
        - name: dateFrom
          in: query
          required: true
          description: Start date (YYYY-MM-DD, inclusive).
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          required: true
          description: End date (YYYY-MM-DD, inclusive).
          schema:
            type: string
            format: date
        - name: merchantId
          in: query
          required: false
          description: Filter by merchant ID.
          schema:
            type: string
        - name: networkId
          in: query
          required: false
          description: Filter by affiliate network.
          schema:
            $ref: "#/components/schemas/NetworkId"
        - name: preset
          in: query
          required: false
          description: Filter by routing preset.
          schema:
            $ref: "#/components/schemas/RoutingPresetId"
        - name: outcome
          in: query
          required: false
          description: Filter by routing outcome.
          schema:
            type: string
            enum:
              - routed
              - fallback
              - passthrough
        - name: linkId
          in: query
          required: false
          description: Filter to a specific link ID.
          schema:
            type: string
        - name: sortBy
          in: query
          required: false
          description: "Sort field. Default: clickedAt."
          schema:
            type: string
            enum:
              - clickedAt
              - normalizedEpcUsd
        - name: sortOrder
          in: query
          required: false
          description: "Sort direction. Default: desc."
          schema:
            type: string
            enum:
              - asc
              - desc
        - name: page
          in: query
          required: false
          description: Page number.
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          required: false
          description: Page size.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Paginated click records.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListClicksResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /analytics/summary:
    get:
      operationId: getAnalyticsSummary
      summary: Get analytics summary
      description: "Aggregated analytics summary: totals, averages, and breakdowns by merchant, network, and preset. Date range is required."
      tags:
        - Analytics
      parameters:
        - name: dateFrom
          in: query
          required: true
          description: Start date (YYYY-MM-DD, inclusive).
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          required: true
          description: End date (YYYY-MM-DD, inclusive).
          schema:
            type: string
            format: date
        - name: granularity
          in: query
          required: false
          description: "Time-series bucketing granularity. Default: month."
          schema:
            type: string
            enum:
              - day
              - week
              - month
        - name: merchantId
          in: query
          required: false
          description: Filter by merchant ID.
          schema:
            type: string
        - name: networkId
          in: query
          required: false
          description: Filter by affiliate network.
          schema:
            $ref: "#/components/schemas/NetworkId"
        - name: preset
          in: query
          required: false
          description: Filter by routing preset.
          schema:
            $ref: "#/components/schemas/RoutingPresetId"
        - name: includeSeries
          in: query
          required: false
          description: "Include time-series data points. Default: false."
          schema:
            type: string
            enum:
              - "true"
              - "false"
        - name: topN
          in: query
          required: false
          description: "Number of top merchants/networks in breakdown. Default: 5."
          schema:
            type: integer
            minimum: 1
            maximum: 20
      responses:
        "200":
          description: Analytics summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetAnalyticsSummaryResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /account:
    get:
      operationId: getAccount
      summary: Get account info
      description: Returns plan tier, click usage, billing period, and trial status for the authenticated creator.
      tags:
        - Account
      responses:
        "200":
          description: Account info.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetAccountResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /fx-rates:
    get:
      operationId: getFxRates
      summary: Get current FX rates
      description: Returns the current FX exchange rates relative to USD, with a staleness flag.
      tags:
        - Platform
      responses:
        "200":
          description: FX rates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetFxRatesResponse"
        "400":
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden — plan upgrade required or account limit reached.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /health:
    get:
      operationId: healthCheck
      summary: Health check
      description: Returns the health status of the API worker. No authentication required.
      tags:
        - Platform
      security: []
      responses:
        "200":
          description: API is healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthCheckResponse"
        "500":
          description: API is degraded or down.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthCheckResponse"
