openapi: 3.1.0
info:
  title: PuppetVendors V2 Vendor API
  version: 2.0.0
  description: |
    The PuppetVendors V2 Vendor API provides programmatic access to vendor operations
    within the PuppetVendors multi-vendor marketplace platform.

    ## Authentication

    Two authentication flows are available:

    ### API Key Flow
    1. Obtain an API key from the vendor portal under Settings > API Keys.
    2. POST the key to `/authenticate` to receive a short-lived JWT token.
    3. Pass the JWT in the `x-access-token` header on all subsequent requests.

    ### Portal Flow
    1. POST email, password, and shopDomain to `/portal/auth/login`.
    2. Receive a JWT token in the response.
    3. Pass the JWT in the `x-access-token` header on all subsequent requests.

    ## Pagination

    List endpoints use cursor-based pagination. Pass `first` (forward) or `last` (backward)
    with optional `after` / `before` cursors. Responses include `pageInfo` with
    `hasNextPage`, `hasPreviousPage`, `startCursor`, and `endCursor`.

    ## Rate Limiting

    Most endpoints share a global rate limit. Certain auth endpoints have stricter
    per-IP limits noted in the endpoint descriptions. When rate-limited you will
    receive a `429` response with a `Retry-After` header.

    ## API Key Scopes

    API keys are scoped to specific permissions:
    - `orders:read` — Read orders and order data
    - `orders:write` — Create timeline entries, trigger exports
    - `products:read` — Read products and product data
    - `products:write` — Create, update, delete products
    - `payouts:read` — Read payout and transaction data
    - `customers:read` — Read customer data
    - `fulfillments:read` — Read fulfillment data
    - `fulfillments:write` — Create and update fulfillments
  contact:
    name: PuppetVendors Support
    url: https://puppetvendors.com
    email: support@puppetvendors.com
  license:
    name: Proprietary

servers:
  - url: https://production-api.puppetvendors.com
    description: Production environment

security:
  - ApiKeyAuth: []

tags:
  - name: Authentication
    description: API key authentication — exchange an API key for a JWT token
  - name: Portal Authentication
    description: Email/password authentication for the vendor portal
  - name: Orders
    description: Order management, listing, filtering, and export
  - name: Products
    description: Product CRUD, inventory management, media, templates, and bulk operations
  - name: Fulfillments
    description: Fulfillment creation, tracking, events, and bulk operations
  - name: Documents
    description: Generate invoices, packing slips, and shipping labels as PDF documents
  - name: Payouts
    description: Payout history, summaries, transactions, and pending items
  - name: Dashboard
    description: Vendor dashboard metrics, charts, and top products
  - name: Customers
    description: Customer listing and export
  - name: Discounts
    description: Vendor discount code management
  - name: Commissions
    description: Commission rate viewing and SKU-level commission management
  - name: Shipping
    description: Shipping rate quotes, label purchasing, address validation, and parcel dimensions
  - name: Profile
    description: Vendor profile and address management
  - name: Settings
    description: Account settings and profile configuration
  - name: Shipping Settings
    description: Shipping rate methods and warehouse management
  - name: Team
    description: Team member management for vendor accounts
  - name: Users
    description: User management (vendor, staff, merchant, admin)
  - name: Integrations
    description: Third-party integration configuration (PayPal, Stripe, Shippo, ShipStation)
  - name: API Keys
    description: API key lifecycle management
  - name: Shop
    description: Shop configuration and feature flags

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-access-token
      description: >-
        JWT access token passed in the `x-access-token` header. For most
        endpoints this may be either a public-API-key JWT (from POST
        `/authenticate`) or a portal-login session JWT (from POST
        `/portal/auth/login`). A subset of endpoints — the password-reset
        token endpoints and the token-return / explicit-override paths of
        `/portal/auth/forgot-password` — require a **public-API-key JWT
        specifically**: a portal-login session token (which carries a user
        `sub`) is rejected with 401 on those routes. See each endpoint's
        description for its exact requirement.

  parameters:
    FirstParam:
      name: first
      in: query
      description: Number of items to return from the beginning of the list (forward pagination). Min 1, max 100.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        example: 25
    AfterParam:
      name: after
      in: query
      description: Cursor to start after (forward pagination). Obtained from `pageInfo.endCursor`.
      schema:
        type: string
        example: "eyJpZCI6IjY1YTFiMmMzZDRlNWY2Nzg5MGFiY2RlZiJ9"
    LastParam:
      name: last
      in: query
      description: Number of items to return from the end of the list (backward pagination). Min 1, max 100.
      schema:
        type: integer
        minimum: 1
        maximum: 100
    BeforeParam:
      name: before
      in: query
      description: Cursor to end before (backward pagination). Obtained from `pageInfo.startCursor`.
      schema:
        type: string
    VendorIdQuery:
      name: vendorId
      in: query
      description: Filter by vendor ID (24-character hex string). Required for API key auth when the key has access to multiple vendors.
      schema:
        type: string
        pattern: "^[0-9a-fA-F]{24}$"
        example: "65a1b2c3d4e5f67890abcdef"
    SearchQuery:
      name: search
      in: query
      description: Free-text search query. Maximum 200 characters.
      schema:
        type: string
        maxLength: 200
        example: "blue widget"
    DateMinQuery:
      name: dateMin
      in: query
      description: Filter results created on or after this ISO 8601 datetime.
      schema:
        type: string
        format: date-time
        example: "2025-01-01T00:00:00.000Z"
    DateMaxQuery:
      name: dateMax
      in: query
      description: Filter results created on or before this ISO 8601 datetime.
      schema:
        type: string
        format: date-time
        example: "2025-12-31T23:59:59.999Z"

  schemas:
    # ── Standard Envelopes ──────────────────────────────────────────────
    SuccessEnvelope:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          description: Whether the request succeeded.
          example: true
      description: Base success response envelope.

    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          required: [message, code]
          properties:
            message:
              type: string
              description: Human-readable error message.
              example: "Validation failed"
            code:
              type: string
              description: >-
                Stable machine-readable error code. The frontend uses it as its
                localisation key (errors.api.<code>). Server errors always report
                INTERNAL_SERVER_ERROR.
              example: "VALIDATION_ERROR"
            errorId:
              type: string
              description: >-
                Correlation id for support/log lookup. Present on opaque 5xx
                responses so a user can quote a reference for the underlying
                (server-side logged) error.
              example: "err_1783365360839_j281w7b"
            details:
              type: object
              description: Additional client-error context, such as field-level validation errors. Never populated for server errors.
              additionalProperties: true
      description: Standard error response envelope.

    PageInfo:
      type: object
      required: [hasNextPage, hasPreviousPage]
      properties:
        hasNextPage:
          type: boolean
          description: Whether more items exist after the last returned cursor.
          example: true
        hasPreviousPage:
          type: boolean
          description: Whether more items exist before the first returned cursor.
          example: false
        startCursor:
          type: string
          description: Cursor of the first item in the current page.
          example: "eyJpZCI6IjY1YTFiMmMzZDRlNWY2Nzg5MGFiY2RlZiJ9"
        endCursor:
          type: string
          description: Cursor of the last item in the current page.
          example: "eyJpZCI6IjY1YjJjM2Q0ZTVmNjc4OTBhYmNkZWYwMSJ9"
      description: Cursor pagination metadata.

    # ── Authentication Schemas ──────────────────────────────────────────
    AuthenticateRequest:
      type: object
      required: [apiKey]
      properties:
        apiKey:
          type: string
          minLength: 1
          description: The vendor API key.
          example: "vk_live_abc123def456"
        shopDomain:
          type: string
          description: Optional Shopify shop domain to scope the token to a specific shop.
          example: "my-store.myshopify.com"
        vendorId:
          type: string
          pattern: "^[0-9a-fA-F]{24}$"
          description: Optional vendor ID (24-character hex) to scope the token to a specific vendor.
          example: "65a1b2c3d4e5f67890abcdef"

    AuthenticateResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [token, expiresIn, scope, shopDomain]
          properties:
            token:
              type: string
              description: JWT access token for authenticating subsequent requests.
              example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            expiresIn:
              type: number
              description: Token lifetime in seconds.
              example: 86400
            scope:
              type: string
              description: Space-separated list of granted scopes.
              example: "orders:read orders:write products:read"
            shopDomain:
              type: string
              description: The shop domain the token is scoped to.
              example: "my-store.myshopify.com"

    VerifyRequest:
      type: object
      required: [apiKey]
      properties:
        apiKey:
          type: string
          minLength: 1
          description: The API key to verify.
          example: "vk_live_abc123def456"

    VerifyResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [valid, scope, shopDomain]
          properties:
            valid:
              type: boolean
              description: Whether the API key is valid and active.
              example: true
            scope:
              type: string
              description: Space-separated scopes granted to the key.
              example: "orders:read products:read"
            shopDomain:
              type: string
              description: Shop domain the key is associated with.
              example: "my-store.myshopify.com"

    RefreshTokenResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [token, expiresIn]
          properties:
            token:
              type: string
              description: New JWT access token.
              example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            expiresIn:
              type: number
              description: Token lifetime in seconds.
              example: 86400

    # ── Portal Authentication Schemas ───────────────────────────────────
    PortalLoginRequest:
      type: object
      required: [email, password, shopDomain]
      properties:
        email:
          type: string
          format: email
          description: Vendor portal email address.
          example: "vendor@example.com"
        password:
          type: string
          description: Account password.
          example: "s3cur3P@ssw0rd"
        shopDomain:
          type: string
          description: The Shopify shop domain to authenticate against.
          example: "my-store.myshopify.com"

    PortalLoginResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [token, expiresIn, scope, shopDomain, user]
          properties:
            token:
              type: string
              description: JWT access token.
              example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            expiresIn:
              type: number
              description: Token lifetime in seconds.
              example: 86400
            scope:
              type: string
              description: Granted scopes.
              example: "orders:read orders:write products:read products:write"
            shopDomain:
              type: string
              example: "my-store.myshopify.com"
            user:
              type: object
              properties:
                id:
                  type: string
                  description: User document ID.
                  example: "65a1b2c3d4e5f67890abcdef"
                email:
                  type: string
                  format: email
                  example: "vendor@example.com"
                vendorId:
                  type: string
                  description: Associated vendor ID.
                  example: "65a1b2c3d4e5f67890abc001"
                vendor:
                  type: string
                  description: Vendor name.
                  example: "Acme Supplies"

    PortalSignupRequest:
      type: object
      required: [email, password, vendor]
      properties:
        email:
          type: string
          format: email
          description: Email address for the new vendor account.
          example: "newvendor@example.com"
        password:
          type: string
          minLength: 6
          description: Password for the new account. Minimum 6 characters.
          example: "s3cur3P@ss"
        shopDomain:
          type: string
          description: Shopify shop domain to register under.
          example: "my-store.myshopify.com"
        shopId:
          type: string
          description: Shop document ID (alternative to shopDomain).
          example: "65a1b2c3d4e5f67890abcdef"
        vendor:
          type: string
          description: Vendor/business name.
          example: "Acme Supplies"
        profile:
          type: object
          description: Optional profile information.
          additionalProperties: true
        firstName:
          type: string
          description: Contact first name.
          example: "Jane"
        lastName:
          type: string
          description: Contact last name.
          example: "Doe"
        phone:
          type: string
          description: Contact phone number.
          example: "+1-555-123-4567"
        message:
          type: string
          description: Optional message to the merchant (e.g. application note).
          example: "We specialize in handmade pottery."
        termsAccepted:
          type: boolean
          description: Whether the vendor accepted the terms of service.
          example: true

    PortalForgotPasswordRequest:
      type: object
      required: [email, shopDomain]
      properties:
        email:
          type: string
          format: email
          description: Email address associated with the vendor account.
          example: "vendor@example.com"
        shopDomain:
          type: string
          description: Shop domain the account belongs to.
          example: "my-store.myshopify.com"
        sendNotification:
          type: boolean
          description: >-
            Optional override of the merchant setting
            notificationSettings.vendorPasswordReset. Omit to use the merchant
            setting (the normal public email flow). Supplying this field at all
            is an explicit override and **requires a public-API-key JWT** in the
            `x-access-token` header (from POST `/authenticate`); a portal-login
            session token is rejected with 401, and a keyless request is
            rejected with 401, regardless of whether the value is true or false.
            With a valid public API key: true forces the email path (a generic
            message is returned even if the merchant has reset emails disabled);
            false forces the token-return path, returning { token, userId } for
            the merchant to deliver. A non-boolean value is rejected with 400.
          example: false

    PortalVendorsResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          description: List of vendors the authenticated user has access to.
          items:
            type: object
            properties:
              _id:
                type: string
                description: Vendor document ID.
                example: "65a1b2c3d4e5f67890abcdef"
              vendorName:
                type: string
                description: Primary vendor name.
                example: "Acme Supplies"
              vendorNameAlias:
                type: string
                description: Optional display alias for the vendor.
                example: "Acme"

    PortalSwitchVendorRequest:
      type: object
      required: [vendorId]
      properties:
        vendorId:
          type: string
          pattern: "^[0-9a-fA-F]{24}$"
          description: ID of the vendor to switch to.
          example: "65a1b2c3d4e5f67890abcdef"

    PortalSwitchVendorResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [token, expiresIn, scope]
          properties:
            token:
              type: string
              description: New JWT token scoped to the selected vendor.
              example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            expiresIn:
              type: number
              example: 86400
            scope:
              type: string
              example: "orders:read products:read"

    # ── Order Schemas ───────────────────────────────────────────────────
    OrderNode:
      type: object
      description: A single order with its line items, fulfillment status, and financial details.
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        orderId:
          type: number
          description: Shopify order ID.
          example: 5678901234
        orderNumber:
          type: number
          description: Human-readable order number.
          example: 1042
        createdAt:
          type: string
          format: date-time
          example: "2025-06-15T10:30:00.000Z"
        updatedAt:
          type: string
          format: date-time
          example: "2025-06-16T08:00:00.000Z"
        status:
          type: string
          enum: [pending, partial, fulfilled, onHold, error, cancelled]
          example: "pending"
        fulfillmentStatus:
          type: string
          example: "unfulfilled"
        financialStatus:
          type: string
          example: "paid"
        vendorPayoutStatus:
          type: string
          enum: [paid, unpaid, partial]
          example: "unpaid"
        totalSales:
          type: number
          description: Total sales amount for vendor line items.
          example: 149.99
        totalPayout:
          type: number
          description: Total payout amount for the vendor.
          example: 127.49
        currency:
          type: string
          example: "USD"
        lineItems:
          type: array
          items:
            type: object
            additionalProperties: true
        customer:
          type: object
          properties:
            firstName:
              type: string
              example: "John"
            lastName:
              type: string
              example: "Smith"
            email:
              type: string
              format: email
              example: "john@example.com"

    OrderCountsResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            pending:
              type: number
              description: Count of orders in pending status.
              example: 12
            partial:
              type: number
              description: Count of partially fulfilled orders.
              example: 3
            fulfilled:
              type: number
              description: Count of fully fulfilled orders.
              example: 87
            onHold:
              type: number
              description: Count of orders on hold.
              example: 1
            error:
              type: number
              description: Count of orders with errors.
              example: 0
            cancelled:
              type: number
              description: Count of cancelled orders.
              example: 2

    TimelineEvent:
      type: object
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        message:
          type: string
          description: Timeline event message.
          example: "Order shipped via FedEx"
        createdAt:
          type: string
          format: date-time
          example: "2025-06-15T10:30:00.000Z"
        createdBy:
          type: string
          description: Who created this event.
          example: "vendor"

    TimelineCreateRequest:
      type: object
      required: [message]
      properties:
        message:
          type: string
          description: The timeline message to add.
          example: "Contacted customer about delivery window"

    # ── Product Schemas ─────────────────────────────────────────────────
    ProductOption:
      type: object
      required: [name, values]
      properties:
        name:
          type: string
          description: Option name (e.g. Size, Color).
          example: "Size"
        values:
          type: array
          items:
            type: string
          description: List of option values.
          example: ["Small", "Medium", "Large"]

    ProductVariantInput:
      type: object
      properties:
        title:
          type: string
          description: Variant title.
          example: "Small / Blue"
        options:
          type: array
          items:
            type: string
          description: Option values for this variant, in the same order as the product options.
          example: ["Small", "Blue"]
        optionValues:
          type: array
          items:
            type: object
            required: [name, value]
            properties:
              name:
                type: string
                description: Option name.
                example: "Size"
              value:
                type: string
                description: Option value.
                example: "Small"
          description: Named option values (alternative to positional options array).
        price:
          type: string
          description: Variant price as a decimal string.
          example: "29.99"
        compareAtPrice:
          type: string
          description: Compare-at (original) price as a decimal string.
          example: "39.99"
        sku:
          type: string
          maxLength: 255
          description: Stock keeping unit.
          example: "WIDGET-SM-BLU"
        barcode:
          type: string
          maxLength: 255
          description: Barcode (UPC, EAN, ISBN, etc.).
          example: "012345678901"
        inventoryQuantity:
          type: integer
          minimum: 0
          description: Available inventory quantity.
          example: 100
        weight:
          type: number
          exclusiveMinimum: 0
          description: Variant weight (must be positive).
          example: 0.5
        weightUnit:
          type: string
          enum: [POUNDS, OUNCES, KILOGRAMS, GRAMS]
          description: Weight unit.
          example: "POUNDS"
        requiresShipping:
          type: boolean
          description: Whether the variant requires shipping.
          example: true
        costOfItem:
          type: string
          description: Cost of goods sold as a decimal string.
          example: "12.50"
        taxable:
          type: boolean
          description: Whether the variant is taxable.
          example: true
        inventoryPolicy:
          type: string
          enum: [DENY, CONTINUE]
          description: Whether to allow purchases when out of stock.
          example: "DENY"
        locationId:
          type: string
          description: Shopify location ID for single-location inventory.
          example: "gid://shopify/Location/12345"
        inventoryQuantities:
          type: array
          items:
            type: object
            required: [locationId, quantity]
            properties:
              locationId:
                type: string
                description: Shopify location ID.
                example: "gid://shopify/Location/12345"
              quantity:
                type: integer
                minimum: 0
                description: Quantity at this location.
                example: 50
          description: Multi-location inventory quantities.
        metafields:
          type: array
          items:
            $ref: "#/components/schemas/MetafieldInput"
          description: Variant-level metafields.

    MetafieldInput:
      type: object
      required: [namespace, key, value]
      properties:
        namespace:
          type: string
          description: Metafield namespace.
          example: "custom"
        key:
          type: string
          description: Metafield key.
          example: "material"
        value:
          type: string
          description: Metafield value.
          example: "organic cotton"
        type:
          type: string
          description: "Metafield type (e.g. single_line_text_field, number_integer)."
          example: "single_line_text_field"
        description:
          type: string
          description: Human-readable description of the metafield.
          example: "Primary material of the product"

    ProductImageInput:
      type: object
      required: [src]
      properties:
        src:
          type: string
          format: uri
          description: URL of the image.
          example: "https://cdn.example.com/images/widget-blue.jpg"
        altText:
          type: string
          maxLength: 255
          description: Alt text for accessibility.
          example: "Blue widget front view"

    ProductSeoInput:
      type: object
      properties:
        title:
          type: string
          maxLength: 255
          description: SEO title tag.
          example: "Blue Widget | Acme Supplies"
        description:
          type: string
          maxLength: 320
          description: SEO meta description.
          example: "Shop our premium blue widget, handcrafted with care."

    ProductCreateRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 255
          description: Product title.
          example: "Premium Blue Widget"
        descriptionHtml:
          type: string
          description: Product description in HTML.
          example: "<p>A beautifully crafted blue widget.</p>"
        vendorId:
          type: string
          description: Vendor ID to assign the product to.
          example: "65a1b2c3d4e5f67890abcdef"
        vendor:
          type: string
          maxLength: 255
          description: Vendor name string.
          example: "Acme Supplies"
        brandName:
          type: string
          maxLength: 255
          description: Brand name for the product.
          example: "Acme"
        productType:
          type: string
          maxLength: 255
          description: Product type/category.
          example: "Widgets"
        tags:
          type: array
          maxItems: 250
          items:
            type: string
          description: Product tags.
          example: ["blue", "premium", "handmade"]
        collections:
          type: array
          maxItems: 250
          items:
            type: string
          description: Collection IDs or handles to add the product to.
          example: ["widgets", "new-arrivals"]
        status:
          type: string
          enum: [ACTIVE, DRAFT, ARCHIVED, PENDING]
          default: DRAFT
          description: Product status. Defaults to DRAFT.
          example: "DRAFT"
        handle:
          type: string
          maxLength: 255
          description: URL handle (slug) for the product.
          example: "premium-blue-widget"
        options:
          type: array
          maxItems: 3
          items:
            $ref: "#/components/schemas/ProductOption"
          description: Product options (max 3, e.g. Size, Color, Material).
        variants:
          type: array
          items:
            $ref: "#/components/schemas/ProductVariantInput"
          description: Product variants.
        metafields:
          type: array
          items:
            $ref: "#/components/schemas/MetafieldInput"
          description: Product-level metafields.
        images:
          type: array
          items:
            $ref: "#/components/schemas/ProductImageInput"
          description: Product images.
        seo:
          $ref: "#/components/schemas/ProductSeoInput"
        category:
          type: string
          description: Shopify product category.
          example: "Home & Garden > Decor"
        templateSuffix:
          type: string
          description: Shopify theme template suffix.
          example: "custom-widget"
        published:
          type: boolean
          description: Whether the product is published to the online store.
          example: true
        isDigital:
          type: boolean
          description: Whether this is a digital product (no shipping required).
          example: false

    ProductUpdateRequest:
      type: object
      description: Same fields as product create, but all are optional.
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 255
          example: "Premium Blue Widget v2"
        descriptionHtml:
          type: string
        vendorId:
          type: string
        vendor:
          type: string
          maxLength: 255
        brandName:
          type: string
          maxLength: 255
        productType:
          type: string
          maxLength: 255
        tags:
          type: array
          maxItems: 250
          items:
            type: string
        collections:
          type: array
          maxItems: 250
          items:
            type: string
        status:
          type: string
          enum: [ACTIVE, DRAFT, ARCHIVED, PENDING]
        handle:
          type: string
          maxLength: 255
        options:
          type: array
          maxItems: 3
          items:
            $ref: "#/components/schemas/ProductOption"
        variants:
          type: array
          items:
            $ref: "#/components/schemas/ProductVariantInput"
        metafields:
          type: array
          items:
            $ref: "#/components/schemas/MetafieldInput"
        images:
          type: array
          items:
            $ref: "#/components/schemas/ProductImageInput"
        seo:
          $ref: "#/components/schemas/ProductSeoInput"
        category:
          type: string
        templateSuffix:
          type: string
        published:
          type: boolean
        isDigital:
          type: boolean

    ProductStatusPatchRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [ACTIVE, DRAFT]
          description: New product status.
          example: "ACTIVE"

    ProductInventoryUpdateRequest:
      type: object
      required: [variants]
      properties:
        variants:
          type: array
          minItems: 1
          items:
            type: object
            required: [variantId, locationId, quantity]
            properties:
              variantId:
                type: string
                description: Shopify variant ID.
                example: "gid://shopify/ProductVariant/12345"
              locationId:
                type: string
                description: Shopify location ID.
                example: "gid://shopify/Location/67890"
              quantity:
                type: integer
                minimum: 0
                description: Inventory quantity to set or adjust.
                example: 50
              inventoryItemId:
                type: string
                description: Optional inventory item ID for direct reference.
                example: "gid://shopify/InventoryItem/11111"
              compareQuantity:
                type: integer
                description: Expected current quantity (for optimistic concurrency in set mode).
                example: 45
          description: Variant inventory updates (at least one required).
        mode:
          type: string
          enum: [set, adjust]
          default: set
          description: "Whether to set the absolute quantity or adjust (add/subtract) from the current quantity."
          example: "set"
        reason:
          type: string
          maxLength: 255
          description: Reason for the inventory change.
          example: "Restock from warehouse"

    ProductMediaRequest:
      type: object
      required: [file]
      properties:
        file:
          type: string
          description: Base64-encoded file content or a public URL.
          example: "https://cdn.example.com/images/product-photo.jpg"
        filename:
          type: string
          maxLength: 255
          description: Original filename.
          example: "product-photo.jpg"
        mediaContentType:
          type: string
          enum: [IMAGE, VIDEO, MODEL_3D, EXTERNAL_VIDEO]
          default: IMAGE
          description: Media content type.
          example: "IMAGE"
        alt:
          type: string
          maxLength: 255
          description: Alt text for the media.
          example: "Product front view"

    ProductAiDescriptionRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          description: Product title to generate a description for.
          example: "Premium Blue Widget"
        productType:
          type: string
          description: Product type for context.
          example: "Widgets"
        tags:
          type: array
          items:
            type: string
          description: Product tags for context.
          example: ["blue", "premium"]
        features:
          type: array
          items:
            type: string
          description: Product features to highlight.
          example: ["Handcrafted", "Eco-friendly materials"]
        imageContext:
          type: string
          description: Description of the product image for additional context.
          example: "A blue ceramic widget on a wooden table"
        merchantCustomPrompt:
          type: string
          description: Custom prompt instructions from the merchant.
          example: "Write in a playful tone, mention our sustainability commitment"

    ProductBulkCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          type: array
          minItems: 1
          maxItems: 10000
          items:
            type: object
            required: [handle, title]
            properties:
              handle:
                type: string
                description: URL handle (must be unique).
                example: "blue-widget-sm"
              title:
                type: string
                description: Product title.
                example: "Blue Widget Small"
              descriptionHtml:
                type: string
              vendor:
                type: string
              productType:
                type: string
              tags:
                type: array
                items:
                  type: string
              status:
                type: string
                enum: [ACTIVE, DRAFT, ARCHIVED, PENDING]
              variants:
                type: array
                items:
                  $ref: "#/components/schemas/ProductVariantInput"
              images:
                type: array
                items:
                  $ref: "#/components/schemas/ProductImageInput"
              options:
                type: array
                maxItems: 3
                items:
                  $ref: "#/components/schemas/ProductOption"
              metafields:
                type: array
                items:
                  $ref: "#/components/schemas/MetafieldInput"
          description: Array of products to create in bulk. Min 1, max 10,000.
        vendorId:
          type: string
          description: Vendor ID to assign all products to.
          example: "65a1b2c3d4e5f67890abcdef"

    ProductNode:
      type: object
      description: A product with its variants, images, and metadata.
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        title:
          type: string
          example: "Premium Blue Widget"
        handle:
          type: string
          example: "premium-blue-widget"
        status:
          type: string
          enum: [active, draft, pending, error, deleted, archived]
          example: "active"
        productType:
          type: string
          example: "Widgets"
        vendor:
          type: string
          example: "Acme Supplies"
        tags:
          type: array
          items:
            type: string
          example: ["blue", "premium"]
        totalInventory:
          type: integer
          example: 150
        createdAt:
          type: string
          format: date-time
          example: "2025-06-01T12:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          example: "2025-06-15T08:30:00.000Z"
        variants:
          type: array
          items:
            type: object
            additionalProperties: true
        images:
          type: array
          items:
            type: object
            additionalProperties: true

    ProductCountsResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            active:
              type: number
              example: 42
            draft:
              type: number
              example: 8
            pending:
              type: number
              example: 3
            error:
              type: number
              example: 0
            deleted:
              type: number
              example: 1
            archived:
              type: number
              example: 5

    # ── Fulfillment Schemas ─────────────────────────────────────────────
    FulfillmentCreateRequest:
      type: object
      required: [lineItemIds]
      properties:
        trackingNumber:
          type: string
          description: Tracking number from the shipping carrier.
          example: "1Z999AA10123456784"
        shippingCarrier:
          type: string
          description: Shipping carrier name.
          example: "UPS"
        trackingUrl:
          type: string
          format: uri
          description: URL to track the shipment.
          example: "https://www.ups.com/track?tracknum=1Z999AA10123456784"
        lineItemIds:
          type: array
          minItems: 1
          items:
            type: string
          description: IDs of the line items to fulfill.
          example: ["65a1b2c3d4e5f67890abcdef", "65a1b2c3d4e5f67890abc001"]
        notifyCustomer:
          type: boolean
          default: false
          description: Whether to send a shipment notification email to the customer.
          example: true

    FulfillmentUpdateRequest:
      type: object
      properties:
        trackingNumber:
          type: string
          description: Updated tracking number.
          example: "1Z999AA10123456785"
        shippingCarrier:
          type: string
          description: Updated carrier name.
          example: "FedEx"
        trackingUrl:
          type: string
          format: uri
          description: Updated tracking URL.
          example: "https://www.fedex.com/fedextrack/?trknbr=1Z999AA10123456785"
        notifyCustomer:
          type: boolean
          description: Whether to notify the customer of the tracking update.

    FulfillmentNode:
      type: object
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        orderNumber:
          type: number
          example: 1042
        fulfillmentStatus:
          type: string
          example: "fulfilled"
        trackingNumber:
          type: string
          example: "1Z999AA10123456784"
        shippingCarrier:
          type: string
          example: "UPS"
        trackingUrl:
          type: string
          example: "https://www.ups.com/track?tracknum=1Z999AA10123456784"
        createdAt:
          type: string
          format: date-time

    FulfillmentCountsResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            fulfilled:
              type: number
              example: 87
            unfulfilled:
              type: number
              example: 15
            onHold:
              type: number
              example: 2
            returnInProgress:
              type: number
              example: 1

    FulfillmentBulkCsvRequest:
      type: object
      required: [csvUrl, vendorId]
      properties:
        csvUrl:
          type: string
          format: uri
          description: URL to a CSV file containing fulfillment data.
          example: "https://cdn.example.com/fulfillments.csv"
        vendorId:
          type: string
          description: Vendor ID to process fulfillments for.
          example: "65a1b2c3d4e5f67890abcdef"
        options:
          type: object
          properties:
            notifyCustomer:
              type: boolean
              default: false
              description: Whether to notify customers for each fulfillment.
            skipErrors:
              type: boolean
              default: true
              description: Whether to skip rows with errors and continue processing.

    FulfillmentEventCreateRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          pattern: "^[\\w]+$"
          description: Event status (Unicode word characters only).
          example: "in_transit"
        message:
          type: string
          maxLength: 500
          description: Optional event message.
          example: "Package has left the sorting facility"
        occurredAt:
          type: string
          format: date-time
          description: When the event occurred. Defaults to now.
          example: "2025-06-15T14:30:00.000Z"
        location:
          type: string
          maxLength: 200
          description: Location where the event occurred.
          example: "Memphis, TN"

    FulfillmentEventUpdateRequest:
      type: object
      properties:
        status:
          type: string
          pattern: "^[\\w]+$"
          example: "delivered"
        message:
          type: string
          maxLength: 500
        occurredAt:
          type: string
          format: date-time
        location:
          type: string
          maxLength: 200

    FulfillmentEventBulkRequest:
      type: object
      required: [events]
      properties:
        events:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required: [lineItemId, status]
            properties:
              lineItemId:
                type: string
                description: Line item ID to attach the event to.
                example: "65a1b2c3d4e5f67890abcdef"
              status:
                type: string
                pattern: "^[\\w]+$"
                example: "in_transit"
              message:
                type: string
                maxLength: 500
              occurredAt:
                type: string
                format: date-time
              location:
                type: string
                maxLength: 200
          description: Batch of fulfillment events to create (min 1, max 100).

    # ── Document Schemas ────────────────────────────────────────────────
    DocumentOrderRequest:
      type: object
      required: [orderIds]
      properties:
        orderIds:
          type: array
          minItems: 1
          items:
            type: number
          description: Shopify order IDs to generate documents for.
          example: [5678901234, 5678901235]

    DocumentPackingSlipRequest:
      type: object
      required: [orderIds]
      properties:
        orderIds:
          type: array
          minItems: 1
          items:
            type: number
          description: Shopify order IDs.
          example: [5678901234]
        lineItemIds:
          type: array
          items:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
          description: Optional line item IDs to filter which items appear on the slip.
          example: ["65a1b2c3d4e5f67890abcdef"]

    DocumentShippingLabelRequest:
      type: object
      required: [orderIds]
      properties:
        orderIds:
          type: array
          minItems: 1
          items:
            type: number
          example: [5678901234]
        lineItemIds:
          type: array
          minItems: 1
          items:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
          description: Line item IDs to include on the shipping label.
          example: ["65a1b2c3d4e5f67890abcdef"]

    DocumentCustomerInvoiceRequest:
      type: object
      required: [orderIds]
      properties:
        orderIds:
          type: array
          minItems: 1
          items:
            type: number
          example: [5678901234]
        vendorId:
          type: string
          pattern: "^[0-9a-fA-F]{24}$"
          description: Optional vendor ID to scope the invoice.
          example: "65a1b2c3d4e5f67890abcdef"

    DocumentGroupedInvoiceRequest:
      type: object
      properties:
        vendorIds:
          type: array
          items:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
          description: Vendor IDs to include in the grouped invoice.
          example: ["65a1b2c3d4e5f67890abcdef"]
        startDate:
          type: string
          format: date-time
          description: Start of the date range.
          example: "2025-01-01T00:00:00.000Z"
        endDate:
          type: string
          format: date-time
          description: End of the date range.
          example: "2025-01-31T23:59:59.999Z"

    DocumentResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [url, expiresAt]
          properties:
            url:
              type: string
              format: uri
              description: Pre-signed URL to download the generated PDF document.
              example: "https://s3.amazonaws.com/puppetvendors/documents/invoice-123.pdf?..."
            expiresAt:
              type: string
              format: date-time
              description: When the download URL expires.
              example: "2025-06-16T10:30:00.000Z"

    # ── Payout Schemas ──────────────────────────────────────────────────
    PayoutNode:
      type: object
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        vendorId:
          type: string
          example: "65a1b2c3d4e5f67890abc001"
        status:
          type: string
          enum: [paid, partial, unpaid]
          example: "paid"
        amount:
          type: number
          description: Payout amount.
          example: 1250.00
        currency:
          type: string
          example: "USD"
        createdAt:
          type: string
          format: date-time
        paidAt:
          type: string
          format: date-time

    PayoutSummaryResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [totalReceived, totalPending, totalScheduled, currency, shopTimezone, itemCount]
          properties:
            totalReceived:
              type: number
              description: Total amount already received by the vendor.
              example: 12500.00
            totalPending:
              type: number
              description: Total amount pending payout.
              example: 3200.50
            totalScheduled:
              type: number
              description: Total amount scheduled for upcoming payouts.
              example: 1500.00
            currency:
              type: string
              description: Currency code.
              example: "USD"
            shopTimezone:
              type: string
              description: Shop timezone for date context.
              example: "America/New_York"
            previousPayout:
              type: object
              description: Details of the most recent payout.
              properties:
                amount:
                  type: number
                  example: 2500.00
                status:
                  type: string
                  example: "paid"
                date:
                  type: string
                  format: date-time
                latestBatchId:
                  type: string
                  example: "65a1b2c3d4e5f67890abcdef"
            nextPayout:
              type: object
              description: Details of the upcoming payout.
              properties:
                amount:
                  type: number
                  example: 1500.00
                status:
                  type: string
                  example: "scheduled"
                estimatedDate:
                  type: string
                  format: date-time
                type:
                  type: string
                  enum: [automated, manual]
                  example: "automated"
                startDate:
                  type: string
                  format: date-time
                endDate:
                  type: string
                  format: date-time
            itemCount:
              type: number
              description: Number of line items included in pending payouts.
              example: 47

    TransactionNode:
      type: object
      properties:
        _id:
          type: string
        vendorId:
          type: string
        status:
          type: string
          enum: [pending, paid, failed, cancelled]
          example: "paid"
        amount:
          type: number
          example: 2500.00
        createdAt:
          type: string
          format: date-time

    # ── Dashboard Schemas ───────────────────────────────────────────────
    DashboardResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          description: Dashboard metrics including sales, orders, and product data.
          additionalProperties: true

    # ── Customer Schemas ────────────────────────────────────────────────
    CustomerNode:
      type: object
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        firstName:
          type: string
          example: "Jane"
        lastName:
          type: string
          example: "Doe"
        email:
          type: string
          format: email
          example: "jane@example.com"
        ordersCount:
          type: number
          example: 5
        totalSpent:
          type: number
          example: 450.00
        createdAt:
          type: string
          format: date-time

    # ── Discount Schemas ────────────────────────────────────────────────
    DiscountCreateRequest:
      type: object
      required: [code, title]
      properties:
        code:
          type: string
          description: Discount code customers enter at checkout.
          example: "SUMMER20"
        title:
          type: string
          description: Internal title for the discount.
          example: "Summer 2025 - 20% off"
        startsAt:
          type: string
          format: date-time
          description: When the discount becomes active.
          example: "2025-06-01T00:00:00.000Z"
        endsAt:
          type: string
          format: date-time
          description: When the discount expires.
          example: "2025-08-31T23:59:59.999Z"
        customerGets:
          type: object
          properties:
            value:
              type: object
              required: [type, value]
              properties:
                type:
                  type: string
                  enum: [percentage, fixed_amount]
                  description: Type of discount value.
                  example: "percentage"
                value:
                  type: number
                  description: Discount value (percentage as 0-100, fixed as currency amount).
                  example: 20
        usageLimit:
          type: integer
          exclusiveMinimum: 0
          description: Maximum number of times the discount can be used.
          example: 500
        appliesOncePerCustomer:
          type: boolean
          description: Whether each customer can only use the discount once.
          example: true

    DiscountUpdateRequest:
      type: object
      description: Same fields as create, all optional.
      properties:
        code:
          type: string
        title:
          type: string
        startsAt:
          type: string
          format: date-time
        endsAt:
          type: string
          format: date-time
        customerGets:
          type: object
          properties:
            value:
              type: object
              properties:
                type:
                  type: string
                  enum: [percentage, fixed_amount]
                value:
                  type: number
        usageLimit:
          type: integer
          exclusiveMinimum: 0
        appliesOncePerCustomer:
          type: boolean

    DiscountNode:
      type: object
      properties:
        _id:
          type: string
          example: "65a1b2c3d4e5f67890abcdef"
        code:
          type: string
          example: "SUMMER20"
        title:
          type: string
          example: "Summer 2025 - 20% off"
        status:
          type: string
          enum: [active, expired, disabled]
          example: "active"
        usageCount:
          type: number
          example: 42
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    # ── Commission Schemas ──────────────────────────────────────────────
    SkuCommissionInput:
      type: object
      required: [commissions]
      properties:
        commissions:
          type: array
          minItems: 1
          maxItems: 500
          items:
            type: object
            required: [sku, rate, type]
            properties:
              sku:
                type: string
                description: Product variant SKU.
                example: "WIDGET-SM-BLU"
              rate:
                type: number
                minimum: 0
                description: Commission rate.
                example: 15
              type:
                type: string
                enum: [percentage, flat]
                description: Whether the rate is a percentage or flat amount.
                example: "percentage"
          description: SKU-level commission overrides (min 1, max 500).

    # ── Shipping Schemas ────────────────────────────────────────────────
    ShippingAddress:
      type: object
      properties:
        street1:
          type: string
          description: Street address line 1.
          example: "123 Main St"
        street2:
          type: string
          description: Street address line 2.
          example: "Suite 4B"
        country:
          type: string
          description: Country code or name.
          example: "US"
        zip:
          type: string
          description: Postal/ZIP code.
          example: "90210"
        city:
          type: string
          description: City name.
          example: "Beverly Hills"
        phone:
          type: string
          description: Phone number.
          example: "+1-555-123-4567"
        state:
          type: string
          description: State or province code.
          example: "CA"
        name:
          type: string
          description: Recipient name.
          example: "Jane Doe"
        email:
          type: string
          format: email
          description: Recipient email.
          example: "jane@example.com"

    GetRatesRequest:
      type: object
      required: [orderNumber, vendorId, lineItems, length, width, height, weight, weightunit, distanceunit, to_address, from_address]
      properties:
        orderNumber:
          oneOf:
            - type: string
            - type: number
          description: Order number to get rates for.
          example: "1042"
        vendorId:
          type: string
          description: Vendor ID.
          example: "65a1b2c3d4e5f67890abcdef"
        lineItems:
          type: array
          minItems: 1
          items:
            type: string
          description: Line item IDs to include in the shipment.
          example: ["65a1b2c3d4e5f67890abcdef"]
        length:
          type: number
          exclusiveMinimum: 0
          description: Package length.
          example: 12
        width:
          type: number
          exclusiveMinimum: 0
          description: Package width.
          example: 8
        height:
          type: number
          exclusiveMinimum: 0
          description: Package height.
          example: 6
        weight:
          type: number
          exclusiveMinimum: 0
          description: Package weight.
          example: 2.5
        weightunit:
          type: string
          description: Weight unit (lb, kg, oz, g).
          example: "lb"
        distanceunit:
          type: string
          description: Distance/dimension unit (in, cm, mm, ft, m, yd).
          example: "in"
        to_address:
          $ref: "#/components/schemas/ShippingAddress"
        from_address:
          $ref: "#/components/schemas/ShippingAddress"

    PurchaseLabelRequest:
      allOf:
        - $ref: "#/components/schemas/GetRatesRequest"
        - type: object
          required: [service_code, shippingCarrier]
          properties:
            service_code:
              type: string
              description: Service code from the rate quote to purchase.
              example: "usps_priority"
            shippingCarrier:
              type: string
              description: Carrier name matching the rate quote.
              example: "USPS"
            carrierCode:
              type: string
              description: Optional carrier-specific code.
              example: "usps"

    GenerateLabelRequest:
      type: object
      required: [vendorId]
      properties:
        vendorId:
          type: string
          description: Vendor ID.
          example: "65a1b2c3d4e5f67890abcdef"
      additionalProperties: true
      description: Label generation request. The vendorId is required; additional fields are passed through to the shipping provider.

    ShippingDimensionCreateRequest:
      type: object
      required: [type, dimensionUnit, heightValue, widthValue, lengthValue]
      properties:
        type:
          type: string
          enum: [generic, productSpecific]
          description: Whether this is a generic default or product-specific dimension.
          example: "generic"
        productId:
          type: string
          description: Product ID (required when type is productSpecific).
          example: "65a1b2c3d4e5f67890abcdef"
        title:
          type: string
          description: Display title for the dimension preset.
          example: "Small Box"
        dimensionUnit:
          type: string
          enum: [in, cm, mm, ft, m, yd]
          description: Unit for the dimension values.
          example: "in"
        heightValue:
          type: number
          minimum: 0
          description: Height value.
          example: 6
        widthValue:
          type: number
          minimum: 0
          description: Width value.
          example: 8
        lengthValue:
          type: number
          minimum: 0
          description: Length value.
          example: 12

    ShippingDimensionUpdateRequest:
      type: object
      description: Partial update — all fields optional.
      properties:
        type:
          type: string
          enum: [generic, productSpecific]
        productId:
          type: string
        title:
          type: string
        dimensionUnit:
          type: string
          enum: [in, cm, mm, ft, m, yd]
        heightValue:
          type: number
          minimum: 0
        widthValue:
          type: number
          minimum: 0
        lengthValue:
          type: number
          minimum: 0

    ValidateAddressRequest:
      type: object
      required: [addressLine1, city, country, pincode, vendorId]
      properties:
        addressLine1:
          type: string
          description: Street address line 1.
          example: "123 Main St"
        addressLine2:
          type: string
          description: Street address line 2.
          example: "Suite 4B"
        city:
          type: string
          description: City.
          example: "Beverly Hills"
        state:
          type: string
          description: State or province.
          example: "CA"
        country:
          type: string
          description: Country code.
          example: "US"
        pincode:
          type: string
          description: Postal/ZIP code.
          example: "90210"
        vendorId:
          type: string
          description: Vendor ID for provider lookup.
          example: "65a1b2c3d4e5f67890abcdef"

    VariantWeightsRequest:
      type: object
      required: [targetUnit]
      properties:
        variantQuantities:
          type: array
          items:
            type: object
            required: [variantId, quantity]
            properties:
              variantId:
                type: string
                description: Variant ID.
                example: "gid://shopify/ProductVariant/12345"
              quantity:
                type: integer
                exclusiveMinimum: 0
                description: Quantity of this variant.
                example: 2
          description: Variants and their quantities to calculate total weight.
        targetUnit:
          type: string
          enum: [g, kg, lb, oz]
          description: Target weight unit for the result.
          example: "lb"

    UnfulfilledQuantityRequest:
      type: object
      required: [lineItemIds]
      properties:
        lineItemIds:
          type: array
          minItems: 1
          items:
            type: string
          description: Line item IDs to check unfulfilled quantities for.
          example: ["65a1b2c3d4e5f67890abcdef"]

    # ── Profile Schemas ─────────────────────────────────────────────────
    VendorProfileUpdateRequest:
      type: object
      required: [fields]
      properties:
        fields:
          type: array
          minItems: 1
          maxItems: 50
          items:
            type: object
            required: [field, value]
            properties:
              field:
                type: string
                description: The profile field name to update.
                example: "vendorName"
              value:
                description: The new value for the field.
                example: "Acme Supplies Inc."
          description: Array of field-value pairs to update on the vendor profile.

    VendorAddressUpdateRequest:
      type: object
      properties:
        address1:
          type: string
          maxLength: 500
          description: Street address line 1.
          example: "456 Commerce Blvd"
        address2:
          type: string
          maxLength: 500
          description: Street address line 2.
          example: "Unit 12"
        city:
          type: string
          maxLength: 200
          description: City.
          example: "Austin"
        state:
          type: string
          maxLength: 200
          description: State or province.
          example: "TX"
        country:
          type: string
          maxLength: 200
          description: Country.
          example: "US"
        zip:
          type: string
          maxLength: 50
          description: Postal/ZIP code.
          example: "78701"
        phone:
          type: string
          maxLength: 50
          description: Phone number.
          example: "+1-512-555-0100"

    # ── Account Settings Schemas ────────────────────────────────────────
    AccountSettingsResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            _id:
              type: string
              example: "65a1b2c3d4e5f67890abcdef"
            email:
              type: string
              format: email
              example: "vendor@example.com"
            profile:
              type: object
              properties:
                firstName:
                  type: string
                  example: "Jane"
                lastName:
                  type: string
                  example: "Doe"
                phone:
                  type: string
                  example: "+1-555-123-4567"
            lastLoggedIn:
              type: string
              format: date-time
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time

    AccountSettingsUpdateRequest:
      type: object
      properties:
        firstName:
          type: string
          maxLength: 100
          description: First name.
          example: "Jane"
        lastName:
          type: string
          maxLength: 100
          description: Last name.
          example: "Doe"
        email:
          type: string
          format: email
          description: New email address.
          example: "newemail@example.com"
        currentPassword:
          type: string
          description: Current password (required when changing password).
          example: "oldP@ssw0rd"
        password:
          type: string
          minLength: 6
          maxLength: 100
          description: New password (min 6 characters).
          example: "newS3cur3P@ss"
        confirmPassword:
          type: string
          description: Must match the new password.
          example: "newS3cur3P@ss"

    ProfileSettingsUpdateRequest:
      type: object
      properties:
        information:
          type: object
          properties:
            businessLogo:
              type: string
              description: URL of the business logo.
              example: "https://cdn.example.com/logo.png"
            businessLogoId:
              type: string
              description: Media ID of the business logo.
            profileBanner:
              type: string
              description: URL of the profile banner image.
            profileBannerId:
              type: string
              description: Media ID of the profile banner.
            description:
              type: string
              description: Short business description.
              example: "Premium handmade goods since 2020"
            profileDescription:
              type: string
              description: Extended profile description.
            businessAddress:
              type: string
              description: Business address for display.
              example: "456 Commerce Blvd, Austin, TX 78701"
        socialMedia:
          type: object
          properties:
            facebook:
              type: string
              maxLength: 2048
              example: "https://facebook.com/acmesupplies"
            instagram:
              type: string
              maxLength: 2048
              example: "https://instagram.com/acmesupplies"
            twitter:
              type: string
              maxLength: 2048
            pinterest:
              type: string
              maxLength: 2048
            snapchat:
              type: string
              maxLength: 2048
            youtube:
              type: string
              maxLength: 2048
            linkedin:
              type: string
              maxLength: 2048
            whatsapp:
              type: string
              maxLength: 2048
            tiktok:
              type: string
              maxLength: 2048
            website:
              type: string
              maxLength: 2048
              example: "https://acmesupplies.com"
        contactInfo:
          type: object
          properties:
            email:
              type: string
              description: Public contact email.
              example: "hello@acmesupplies.com"
            phone:
              type: string
              description: Public contact phone.
              example: "+1-512-555-0100"
            location:
              type: string
              description: Public location display text.
              example: "Austin, TX"
        policies:
          type: object
          properties:
            shippingPolicy:
              type: string
              maxLength: 5000
              description: Shipping policy text.
              example: "Free shipping on orders over $50. Standard delivery 5-7 business days."
            returnsPolicy:
              type: string
              maxLength: 5000
              description: Returns policy text.
              example: "30-day returns on all items in original condition."
        vacation:
          type: object
          properties:
            enabled:
              type: boolean
              description: Whether vacation mode is active.
              example: false
            timeRange:
              type: array
              minItems: 2
              maxItems: 2
              items:
                type: string
                format: date-time
              description: Start and end datetime for vacation mode.
              example: ["2025-07-01T00:00:00.000Z", "2025-07-15T23:59:59.999Z"]
            message:
              type: string
              maxLength: 500
              description: Message displayed to customers during vacation.
              example: "We're on vacation until July 15th. Orders will ship after we return."
        others:
          type: object
          properties:
            hasTax:
              type: boolean
              description: Whether the vendor charges tax.
              example: true
            vendorTaxRate:
              type: number
              minimum: 0
              maximum: 100
              description: Tax rate percentage (0-100).
              example: 8.25
        customFields:
          type: object
          additionalProperties: true
          description: Custom fields defined by the merchant.

    # ── Shipping Settings Schemas ───────────────────────────────────────
    ShippingRateUpdateRequest:
      oneOf:
        - type: object
          required: [method, amount]
          properties:
            method:
              type: string
              enum: [order]
              description: Flat rate per order.
              example: "order"
            amount:
              type: number
              minimum: 0
              description: Flat shipping amount per order.
              example: 5.99
        - type: object
          required: [method, amount]
          properties:
            method:
              type: string
              enum: [lineItem]
              description: Flat rate per line item.
              example: "lineItem"
            amount:
              type: number
              minimum: 0
              description: Shipping amount per line item.
              example: 2.99
        - type: object
          required: [method, ranges]
          properties:
            method:
              type: string
              enum: [orderRange]
              description: Tiered rate based on order total.
              example: "orderRange"
            ranges:
              type: array
              items:
                type: object
                required: [from, to, rate]
                properties:
                  from:
                    type: number
                    description: Range start (order total).
                    example: 0
                  to:
                    type: number
                    description: Range end (order total).
                    example: 50
                  rate:
                    type: number
                    description: Shipping rate for this range.
                    example: 7.99
        - type: object
          required: [method, ranges]
          properties:
            method:
              type: string
              enum: [orderWeight]
              description: Tiered rate based on order weight.
              example: "orderWeight"
            ranges:
              type: array
              items:
                type: object
                required: [from, to, rate]
                properties:
                  from:
                    type: number
                    example: 0
                  to:
                    type: number
                    example: 5
                  rate:
                    type: number
                    example: 9.99
        - type: object
          required: [method]
          properties:
            method:
              type: string
              enum: [realtime]
              description: Real-time rates from a shipping provider.
              example: "realtime"
            warehouseId:
              type: string
              description: Warehouse to calculate rates from.
              example: "65a1b2c3d4e5f67890abcdef"
            realtimeOption:
              type: string
              enum: [fastest, cheapest]
              description: How to select among multiple rate options.
              example: "cheapest"
      discriminator:
        propertyName: method

    WarehouseCreateRequest:
      type: object
      required: [name, address1, city, zip, country]
      properties:
        name:
          type: string
          maxLength: 200
          description: Warehouse name.
          example: "Main Warehouse"
        address1:
          type: string
          maxLength: 500
          description: Street address line 1.
          example: "789 Industrial Pkwy"
        address2:
          type: string
          maxLength: 500
          description: Street address line 2.
          example: "Building C"
        city:
          type: string
          maxLength: 200
          description: City.
          example: "Dallas"
        province:
          type: string
          maxLength: 200
          description: State/province.
          example: "TX"
        zip:
          type: string
          maxLength: 50
          description: Postal/ZIP code.
          example: "75201"
        country:
          type: string
          maxLength: 200
          description: Country.
          example: "US"
        phone:
          type: string
          maxLength: 50
          description: Warehouse phone.
          example: "+1-214-555-0200"
        email:
          type: string
          maxLength: 200
          description: Warehouse contact email.
          example: "warehouse@acmesupplies.com"
        defaultShippingAddress:
          type: boolean
          description: Whether this is the default shipping origin.
          example: true

    WarehouseUpdateRequest:
      type: object
      description: Partial warehouse update — all fields optional.
      properties:
        name:
          type: string
          maxLength: 200
        address1:
          type: string
          maxLength: 500
        address2:
          type: string
          maxLength: 500
        city:
          type: string
          maxLength: 200
        province:
          type: string
          maxLength: 200
        zip:
          type: string
          maxLength: 50
        country:
          type: string
          maxLength: 200
        phone:
          type: string
          maxLength: 50
        email:
          type: string
          maxLength: 200
        defaultShippingAddress:
          type: boolean

    # ── Team Schemas ────────────────────────────────────────────────────
    TeamMemberCreateRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
          description: Team member email address.
          example: "team@example.com"
        password:
          type: string
          minLength: 6
          maxLength: 100
          description: Initial password for the team member.
          example: "t3amP@ss"
        profile:
          type: object
          properties:
            firstName:
              type: string
              maxLength: 100
              example: "Alex"
            lastName:
              type: string
              maxLength: 100
              example: "Johnson"
            phone:
              type: string
              maxLength: 50
              example: "+1-555-000-1234"
        apiPermissions:
          type: array
          items:
            type: string
          description: Permission scopes for the team member.
          example: ["orders:read", "products:read", "products:write"]

    TeamMemberUpdateRequest:
      type: object
      description: Partial update — all fields optional.
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 6
          maxLength: 100
        profile:
          type: object
          properties:
            firstName:
              type: string
              maxLength: 100
            lastName:
              type: string
              maxLength: 100
            phone:
              type: string
              maxLength: 50
        apiPermissions:
          type: array
          items:
            type: string

    # ── User Schemas ────────────────────────────────────────────────────
    UserCreateRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
          description: User email.
          example: "newuser@example.com"
        password:
          type: string
          minLength: 6
          maxLength: 100
          description: User password.
          example: "us3rP@ss"
        vendorId:
          type: string
          description: Vendor ID to associate the user with.
          example: "65a1b2c3d4e5f67890abcdef"
        vendor:
          type: string
          maxLength: 200
          description: Vendor name (alternative to vendorId for new vendor creation).
          example: "Acme Supplies"
        type:
          type: string
          enum: [vendor, staff, merchant, admin]
          default: vendor
          description: User type.
          example: "vendor"
        apiPermissions:
          type: array
          items:
            type: string
          description: API permission scopes.
          example: ["orders:read", "products:read"]
        approved:
          type: boolean
          default: false
          description: Whether the user is pre-approved.
          example: false
        notifications:
          type: object
          additionalProperties: true
          description: Notification preferences.
        sendInvite:
          type: boolean
          default: false
          description: Whether to send an invitation email.
          example: true
        profile:
          type: object
          properties:
            firstName:
              type: string
              example: "Sam"
            lastName:
              type: string
              example: "Wilson"
            phone:
              type: string
              example: "+1-555-000-5678"
        permission:
          type: object
          additionalProperties: true
          description: Granular permission object.

    UserUpdateRequest:
      type: object
      description: Partial user update.
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 6
          maxLength: 100
        vendorId:
          type: string
        vendor:
          type: string
          maxLength: 200
        type:
          type: string
          enum: [vendor, staff, merchant, admin]
        apiPermissions:
          type: array
          items:
            type: string
        approved:
          type: boolean
        notifications:
          type: object
          additionalProperties: true
        sendInvite:
          type: boolean
        profile:
          type: object
          properties:
            firstName:
              type: string
            lastName:
              type: string
            phone:
              type: string
        permission:
          type: object
          additionalProperties: true

    # ── Integration Schemas ─────────────────────────────────────────────
    PaypalUpdateRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
          maxLength: 200
          description: PayPal email address for receiving payouts.
          example: "payouts@acmesupplies.com"

    PaypalResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            configured:
              type: boolean
              description: Whether PayPal is configured.
              example: true
            maskedEmail:
              type: string
              description: Masked email for display.
              example: "pay***@acmesupplies.com"

    StripeConnectRequest:
      type: object
      required: [accountType, returnUrl]
      properties:
        accountType:
          type: string
          enum: [express, standard]
          description: Type of Stripe Connect account.
          example: "express"
        returnUrl:
          type: string
          format: uri
          maxLength: 2000
          description: URL to redirect to after Stripe onboarding completes.
          example: "https://vendor-portal.example.com/settings/integrations"

    StripeConnectResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            onboardingUrl:
              type: string
              format: uri
              description: Stripe onboarding URL to redirect the vendor to.
              example: "https://connect.stripe.com/setup/..."

    StripeStatusResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            configured:
              type: boolean
              example: true
            accountId:
              type: string
              description: Stripe Connect account ID.
              example: "acct_1234567890"
            accountType:
              type: string
              enum: [express, standard]
              example: "express"
            chargesEnabled:
              type: boolean
              description: Whether the account can accept charges.
              example: true
            payoutsEnabled:
              type: boolean
              description: Whether the account can receive payouts.
              example: true
            detailsSubmitted:
              type: boolean
              description: Whether all required account details have been submitted.
              example: true

    ShippingProviderUpdateRequest:
      type: object
      required: [apiKey]
      properties:
        apiKey:
          type: string
          minLength: 1
          maxLength: 500
          pattern: "^[\\x20-\\x7E]+$"
          description: API key for the shipping provider (printable ASCII only).
          example: "shippo_live_abc123..."

    ShippingProviderResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            configured:
              type: boolean
              description: Whether the integration is configured.
              example: true
            maskedApiKey:
              type: string
              description: Masked API key for display.
              example: "ship***...c123"
            verificationStatus:
              type: string
              enum: [verified, unreachable]
              description: Whether the API key was successfully verified.
              example: "verified"

    # ── API Key Schemas ─────────────────────────────────────────────────
    ApiKeyNode:
      type: object
      properties:
        id:
          type: string
          description: API key document ID.
          example: "65a1b2c3d4e5f67890abcdef"
        label:
          type: string
          description: Human-readable label for the key.
          example: "Production Key"
        lastFour:
          type: string
          description: Last four characters of the key for identification.
          example: "f456"
        prefix:
          type: string
          enum: [vk_live, vk_test]
          description: Key prefix indicating live or test mode.
          example: "vk_live"
        mode:
          type: string
          enum: [live, test]
          description: Key mode.
          example: "live"
        scopes:
          type: array
          items:
            type: string
          description: Granted permission scopes.
          example: ["orders:read", "products:read", "products:write"]
        createdAt:
          type: string
          format: date-time
          example: "2025-06-01T12:00:00.000Z"
        createdBy:
          type: string
          description: User who created the key.
          example: "65a1b2c3d4e5f67890abcdef"
        lastUsedAt:
          type: string
          format: date-time
          description: When the key was last used.
        revokedAt:
          type: string
          format: date-time
          description: When the key was revoked (null if active).
        isLegacy:
          type: boolean
          description: Whether this is a legacy API key.
          example: false

    ApiKeyCreateRequest:
      type: object
      required: [label, scopes]
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 64
          pattern: "^[A-Za-z0-9 _.\\-]+$"
          description: Human-readable label (alphanumeric, spaces, underscores, dots, hyphens).
          example: "Production Key"
        mode:
          type: string
          enum: [live, test]
          default: live
          description: Key mode.
          example: "live"
        scopes:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - orders:read
              - orders:write
              - products:read
              - products:write
              - payouts:read
              - customers:read
              - fulfillments:read
              - fulfillments:write
          description: Permission scopes to grant to the key.
          example: ["orders:read", "products:read", "products:write"]

    ApiKeyCreateResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          allOf:
            - $ref: "#/components/schemas/ApiKeyNode"
            - type: object
              required: [token]
              properties:
                token:
                  type: string
                  description: |
                    The full API key token. This is shown only once at creation time
                    and cannot be retrieved again. Store it securely.
                  example: "vk_live_abc123def456ghi789jkl012mno345"

    ApiKeyRotateResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required: [token]
          properties:
            token:
              type: string
              description: |
                The new API key token after rotation. The previous token is immediately
                invalidated. Store the new token securely — it cannot be retrieved again.
              example: "vk_live_xyz789abc123def456ghi789jkl012"

  responses:
    BadRequest:
      description: The request body or query parameters failed validation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Validation failed"
              code: "VALIDATION_ERROR"
              details:
                email: "Must be a valid email address"
    Unauthorized:
      description: Missing or invalid authentication token.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Authentication required"
              code: "UNAUTHORIZED"
    Forbidden:
      description: The authenticated user does not have permission for this operation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Insufficient permissions"
              code: "FORBIDDEN"
    NotFound:
      description: The requested resource was not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Resource not found"
              code: "NOT_FOUND"
    RateLimited:
      description: Too many requests. Retry after the time indicated in the `Retry-After` header.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            example: 60
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Too many requests"
              code: "RATE_LIMITED"
    ServerError:
      description: An unexpected server error occurred.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              message: "Internal server error"
              code: "INTERNAL_ERROR"

# ═══════════════════════════════════════════════════════════════════════
# PATHS
# ═══════════════════════════════════════════════════════════════════════
paths:
  # ── Authentication ────────────────────────────────────────────────────
  /authenticate:
    post:
      operationId: authenticate
      summary: Exchange API key for JWT token
      description: |
        Authenticates a vendor API key and returns a short-lived JWT token.
        The token should be passed in the `x-access-token` header for all
        subsequent authenticated requests.
      tags: [Authentication]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AuthenticateRequest"
      responses:
        "200":
          description: Authentication successful.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /verify:
    post:
      operationId: verifyApiKey
      summary: Verify an API key
      description: Checks whether an API key is valid and returns its scope and associated shop domain without issuing a token.
      tags: [Authentication]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyRequest"
      responses:
        "200":
          description: Verification result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/ServerError"

  /refresh-token:
    post:
      operationId: refreshToken
      summary: Refresh JWT token
      description: Exchanges a valid (non-expired) JWT token for a new one with a fresh expiry.
      tags: [Authentication]
      responses:
        "200":
          description: Token refreshed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefreshTokenResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Portal Authentication ─────────────────────────────────────────────
  /portal/auth/login:
    post:
      operationId: portalLogin
      summary: Vendor portal login
      description: |
        Authenticates a vendor using email and password. Returns a JWT token
        and user details including the associated vendor.
      tags: [Portal Authentication]
      security: []
      x-rate-limit: "5 requests per 15 minutes per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PortalLoginRequest"
      responses:
        "200":
          description: Login successful.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortalLoginResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /portal/auth/signup:
    post:
      operationId: portalSignup
      summary: Vendor portal signup
      description: |
        Registers a new vendor account. The account may require merchant approval
        before the vendor can access the portal.
      tags: [Portal Authentication]
      security: []
      x-rate-limit: "3 requests per 15 minutes per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PortalSignupRequest"
      responses:
        "201":
          description: Signup successful. Account may be pending approval.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: "Registration successful. Your account is pending approval."
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /portal/auth/forgot-password:
    post:
      operationId: portalForgotPassword
      summary: Request password reset
      description: >-
        Requests a vendor password reset. Delivery is decided by the merchant
        setting notificationSettings.vendorPasswordReset, unless overridden by
        the request body field sendNotification. Enabled or unset (default): PV
        emails the reset link and returns a generic message. Disabled: PV
        suppresses the email and returns { token, userId } so the merchant
        delivers it — that token-return path requires a public-API-key JWT for
        the shop.


        The normal public email flow (no `sendNotification` in the body, no
        token) needs no authentication and is reCAPTCHA-protected. Any explicit
        `sendNotification` override — **true OR false** — requires a
        public-API-key JWT (from POST `/authenticate`) in the `x-access-token`
        header. A portal-login session token does not satisfy this and is
        rejected with 401; a keyless explicit override is likewise rejected with
        401. The token-return path (merchant setting disabled, or
        `sendNotification: false`) is therefore only reachable with a public API
        key.
      tags: [Portal Authentication]
      security: []
      x-rate-limit: "3 requests per 15 minutes per IP"
      parameters:
        - in: header
          name: x-access-token
          required: false
          schema:
            type: string
          description: >-
            Public-API-key JWT (from POST `/authenticate`). Required whenever the
            request supplies any explicit `sendNotification` override (true OR
            false) and required for the token-return path (when the shop has
            notificationSettings.vendorPasswordReset disabled). Must be a public
            API key — a portal-login session token is rejected with 401. Leave
            blank only for the normal public email flow (no `sendNotification`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PortalForgotPasswordRequest"
      responses:
        "200":
          description: >-
            Email mode (flag enabled/unset) returns a generic message; token mode
            (flag disabled) returns { token, userId } for the merchant to deliver.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
              examples:
                email:
                  value:
                    success: true
                    data:
                      message: "If your account exists, you will receive an email with instructions to reset your password."
                token:
                  value:
                    success: true
                    data:
                      token: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
                      userId: "507f1f77bcf86cd799439011"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          description: >-
            A public-API-key JWT was required but not supplied (or a portal-login
            session token was supplied instead). This covers two cases: (a) any
            explicit `sendNotification` override (true OR false) without a public
            API key, and (b) the token-return path (reset emails disabled for
            this shop, or `sendNotification: false`) without a public API key.
            Error message for the override case: "A public API key is required to
            override notification delivery (sendNotification)".
        "403":
          description: API key is not authorized for the requested shop (shop-domain mismatch on the token-return path).
        "404":
          description: Token-return path only (merchant setting disabled or sendNotification false) — no account matches the email for this shop (the email path never reveals this).
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /password-reset/verify:
    post:
      operationId: verifyPasswordResetToken
      summary: Verify a password-reset token
      description: >-
        Server-to-server: check whether a forgot-password reset token is valid
        and not expired, without consuming it. Requires a public-API-key JWT
        (x-access-token from POST `/authenticate`) — this endpoint is key-only,
        so a portal-login session token (a user session) is rejected with 401.
        The lookup is scoped to the key's shop, and a vendor-scoped key only
        resolves its own vendor's users. Always responds 200 with
        { valid, reason? } and no PII.
      tags: [Password Reset]
      security:
        - ApiKeyAuth: []
      x-rate-limit: "30 requests per 15 minutes per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userId, token]
              properties:
                userId:
                  type: string
                  description: 24-char hex ObjectId from the reset link.
                  example: "507f1f77bcf86cd799439011"
                token:
                  type: string
                  description: 32-char hex reset token from the reset link.
                  example: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
      responses:
        "200":
          description: Verification result (always 200; check data.valid).
          content:
            application/json:
              examples:
                valid:
                  value: { success: true, data: { valid: true } }
                invalid:
                  value: { success: true, data: { valid: false, reason: invalid } }
                expired:
                  value: { success: true, data: { valid: false, reason: expired } }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          description: >-
            Missing, invalid, or non-public-API-key token. A portal-login session
            token (user session) is rejected here — this endpoint requires a
            public-API-key JWT (x-access-token).
        "429":
          $ref: "#/components/responses/RateLimited"

  /password-reset/confirm:
    post:
      operationId: confirmPasswordReset
      summary: Set a new password using a reset token
      description: >-
        Server-to-server: consume a valid reset token and set the user's new
        password (across all of that email's accounts in the shop). Requires a
        public-API-key JWT (x-access-token from POST `/authenticate`) — this
        endpoint is key-only, so a portal-login session token (a user session)
        is rejected with 401. A bad or expired token returns 400.
      tags: [Password Reset]
      security:
        - ApiKeyAuth: []
      x-rate-limit: "10 requests per 15 minutes per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userId, token, newPassword, confirmPassword]
              properties:
                userId:
                  type: string
                  example: "507f1f77bcf86cd799439011"
                token:
                  type: string
                  example: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
                newPassword:
                  type: string
                  minLength: 6
                confirmPassword:
                  type: string
                  minLength: 6
      responses:
        "200":
          description: Password updated.
          content:
            application/json:
              example:
                success: true
                data:
                  message: Password updated successfully
        "400":
          description: Invalid or expired token, validation error, or passwords do not match.
        "401":
          description: >-
            Missing, invalid, or non-public-API-key token. A portal-login session
            token (user session) is rejected here — this endpoint requires a
            public-API-key JWT (x-access-token).
        "429":
          $ref: "#/components/responses/RateLimited"

  /portal/auth/logout:
    post:
      operationId: portalLogout
      summary: Vendor portal logout
      description: Invalidates the current JWT token.
      tags: [Portal Authentication]
      responses:
        "200":
          description: Logout successful.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /portal/auth/vendors:
    get:
      operationId: portalListVendors
      summary: List accessible vendors
      description: Returns all vendors the authenticated user has access to. Useful for users with multi-vendor access to populate a vendor switcher.
      tags: [Portal Authentication]
      responses:
        "200":
          description: List of accessible vendors.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortalVendorsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /portal/auth/switch-vendor:
    post:
      operationId: portalSwitchVendor
      summary: Switch active vendor
      description: Switches the authenticated session to a different vendor. Returns a new JWT token scoped to the selected vendor.
      tags: [Portal Authentication]
      x-rate-limit: "10 requests per minute per IP"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PortalSwitchVendorRequest"
      responses:
        "200":
          description: Vendor switched successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortalSwitchVendorResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /portal/auth/refresh-token:
    post:
      operationId: portalRefreshToken
      summary: Refresh portal JWT token
      description: Refreshes the portal JWT token.
      tags: [Portal Authentication]
      responses:
        "200":
          description: Token refreshed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefreshTokenResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Orders ────────────────────────────────────────────────────────────
  /orders:
    get:
      operationId: listOrders
      summary: List orders
      description: Returns a paginated list of orders for the vendor, with cursor-based pagination and extensive filtering options.
      tags: [Orders]
      x-scopes: ["orders:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: startDate
          in: query
          description: Filter orders created on or after this ISO 8601 datetime.
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          description: Filter orders created on or before this ISO 8601 datetime.
          schema:
            type: string
            format: date-time
        - name: locationId
          in: query
          description: Filter by Shopify location ID.
          schema:
            type: number
            example: 12345678
        - $ref: "#/components/parameters/SearchQuery"
        - name: status
          in: query
          description: Filter by order fulfillment status.
          schema:
            type: string
            enum: [pending, partial, fulfilled, onHold, error, cancelled]
        - name: fulfillmentStatus
          in: query
          description: Filter by raw Shopify fulfillment status.
          schema:
            type: string
        - name: vendorPayoutStatus
          in: query
          description: Filter by vendor payout status.
          schema:
            type: string
            enum: [paid, unpaid, partial]
        - name: financialStatus
          in: query
          description: Filter by financial status.
          schema:
            type: string
        - name: sortBy
          in: query
          description: Field to sort results by.
          schema:
            type: string
            enum: [createdAt, orderId, orderNumber, shipDate, deliveryDate, totalSales, totalPayout]
            default: createdAt
        - name: sortOrder
          in: query
          description: Sort direction.
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        "200":
          description: Paginated list of orders.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/OrderNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
                        example: 142
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /orders/counts:
    get:
      operationId: getOrderCounts
      summary: Get order counts by status
      description: Returns the count of orders grouped by fulfillment status.
      tags: [Orders]
      x-scopes: ["orders:read"]
      responses:
        "200":
          description: Order counts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderCountsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Get order details
      description: Returns full details for a single order including all line items, fulfillments, and calculations.
      tags: [Orders]
      x-scopes: ["orders:read"]
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order document ID or Shopify order ID.
          schema:
            type: string
            example: "65a1b2c3d4e5f67890abcdef"
      responses:
        "200":
          description: Order details.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/OrderNode"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /orders/export:
    get:
      operationId: exportOrders
      summary: Export orders as CSV
      description: |
        Exports orders matching the given filters as a CSV file. Returns:
        - **200**: CSV stream (small datasets)
        - **202**: Export queued for background processing (large datasets)
        - **204**: No matching orders
      tags: [Orders]
      x-scopes: ["orders:read"]
      parameters:
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: startDate
          in: query
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          schema:
            type: string
            format: date-time
        - $ref: "#/components/parameters/SearchQuery"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, partial, fulfilled, onHold, error, cancelled]
        - name: vendorPayoutStatus
          in: query
          schema:
            type: string
            enum: [paid, unpaid, partial]
      responses:
        "200":
          description: CSV file stream.
          content:
            text/csv:
              schema:
                type: string
        "202":
          description: Export queued. A download link will be sent via notification.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: "Export queued. You will be notified when it is ready."
        "204":
          description: No orders match the given filters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /orders/export/products:
    get:
      operationId: exportOrderProducts
      summary: Export order products as CSV
      description: Exports order line-item product data as CSV. Same response behavior as order export (200/202/204).
      tags: [Orders]
      x-scopes: ["orders:read"]
      parameters:
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: startDate
          in: query
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          schema:
            type: string
            format: date-time
        - $ref: "#/components/parameters/SearchQuery"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, partial, fulfilled, onHold, error, cancelled]
      responses:
        "200":
          description: CSV file stream.
          content:
            text/csv:
              schema:
                type: string
        "202":
          description: Export queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "204":
          description: No data.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /orders/{orderId}/timeline:
    get:
      operationId: getOrderTimeline
      summary: Get order timeline
      description: Returns timeline events for a specific order, including system events, notes, and status changes.
      tags: [Orders]
      x-scopes: ["orders:read"]
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Timeline events.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TimelineEvent"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createOrderTimelineEntry
      summary: Add order timeline entry
      description: Adds a new timeline entry (note) to an order.
      tags: [Orders]
      x-scopes: ["orders:write"]
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TimelineCreateRequest"
      responses:
        "201":
          description: Timeline entry created.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/TimelineEvent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Products ──────────────────────────────────────────────────────────
  /products:
    get:
      operationId: listProducts
      summary: List products
      description: Returns a paginated list of products with filtering by status, inventory, and search.
      tags: [Products]
      x-scopes: ["products:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: status
          in: query
          description: Filter by product status. Can be a single value, comma-separated values, or repeated query params.
          schema:
            oneOf:
              - type: string
                enum: [active, draft, pending, error, deleted, archived]
              - type: array
                items:
                  type: string
                  enum: [active, draft, pending, error, deleted, archived]
          example: "active,draft"
        - name: inventoryStatus
          in: query
          description: Filter by inventory availability.
          schema:
            type: string
            enum: [inStock, outOfStock]
        - name: createdAtMin
          in: query
          description: Filter products created on or after this date.
          schema:
            type: string
            format: date-time
        - name: createdAtMax
          in: query
          description: Filter products created on or before this date.
          schema:
            type: string
            format: date-time
        - $ref: "#/components/parameters/SearchQuery"
        - name: sortBy
          in: query
          schema:
            type: string
            enum: [title, totalInventory, createdAt, updatedAt]
            default: updatedAt
        - name: sortOrder
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        "200":
          description: Paginated product list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/ProductNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
                        example: 85
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createProduct
      summary: Create a product
      description: Creates a new product and optionally syncs it to Shopify. The product will be created in DRAFT status unless otherwise specified.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductCreateRequest"
      responses:
        "201":
          description: Product created.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/ProductNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/counts:
    get:
      operationId: getProductCounts
      summary: Get product counts by status
      description: Returns the count of products grouped by status.
      tags: [Products]
      x-scopes: ["products:read"]
      responses:
        "200":
          description: Product counts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductCountsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/{productId}:
    get:
      operationId: getProduct
      summary: Get product details
      description: Returns full details for a single product including all variants, images, and metadata.
      tags: [Products]
      x-scopes: ["products:read"]
      parameters:
        - name: productId
          in: path
          required: true
          description: Product document ID.
          schema:
            type: string
            example: "65a1b2c3d4e5f67890abcdef"
      responses:
        "200":
          description: Product details.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/ProductNode"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateProduct
      summary: Update a product
      description: Updates an existing product. Only fields included in the request body are changed.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductUpdateRequest"
      responses:
        "200":
          description: Product updated.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/ProductNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteProduct
      summary: Delete a product
      description: Deletes a product. This marks the product as deleted and removes it from Shopify.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Product deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/{productId}/status:
    patch:
      operationId: updateProductStatus
      summary: Update product status
      description: Changes a product's status to ACTIVE or DRAFT.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductStatusPatchRequest"
      responses:
        "200":
          description: Status updated.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/ProductNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/{productId}/duplicate:
    post:
      operationId: duplicateProduct
      summary: Duplicate a product
      description: Creates a copy of an existing product with a new title and handle.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Product duplicated.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/ProductNode"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/{productId}/inventory:
    patch:
      operationId: updateProductInventory
      summary: Update product inventory
      description: |
        Updates inventory quantities for one or more variants at specific locations.
        Supports both `set` (absolute) and `adjust` (relative) modes.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductInventoryUpdateRequest"
      responses:
        "200":
          description: Inventory updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/media:
    post:
      operationId: uploadProductMedia
      summary: Upload product media
      description: Uploads media (image, video, 3D model) for use with products.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductMediaRequest"
      responses:
        "201":
          description: Media uploaded.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      url:
                        type: string
                        format: uri
                      mediaContentType:
                        type: string
                      alt:
                        type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/export:
    get:
      operationId: exportProducts
      summary: Export products as CSV
      description: Exports products matching filters as a CSV file.
      tags: [Products]
      x-scopes: ["products:read"]
      parameters:
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: status
          in: query
          schema:
            type: string
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: CSV file stream.
          content:
            text/csv:
              schema:
                type: string
        "202":
          description: Export queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "204":
          description: No products match the filters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/templates:
    get:
      operationId: listProductTemplates
      summary: List product templates
      description: Returns available product templates that can be used to pre-fill new product forms.
      tags: [Products]
      x-scopes: ["products:read"]
      responses:
        "200":
          description: Product templates.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createProductTemplate
      summary: Create a product template
      description: Creates a new product template.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              description: Template data matching the product create schema.
      responses:
        "201":
          description: Template created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/templates/{id}:
    get:
      operationId: getProductTemplate
      summary: Get a product template
      description: Returns a specific product template by ID.
      tags: [Products]
      x-scopes: ["products:read"]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Product template.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteProductTemplate
      summary: Delete a product template
      description: Deletes a product template.
      tags: [Products]
      x-scopes: ["products:write"]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Template deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/catalog:
    get:
      operationId: getProductCatalog
      summary: Get product catalog
      description: Returns the product catalog — a curated view of products available from the vendor.
      tags: [Products]
      x-scopes: ["products:read"]
      responses:
        "200":
          description: Product catalog.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/barcode:
    post:
      operationId: generateBarcode
      summary: Generate a barcode
      description: Generates a barcode image for a product or variant.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Barcode generated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/ai/description:
    post:
      operationId: generateAiDescription
      summary: Generate AI product description
      description: Uses AI to generate a product description based on the title, type, tags, features, and optional image context.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductAiDescriptionRequest"
      responses:
        "200":
          description: AI-generated description.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      description:
                        type: string
                        description: Generated HTML description.
                        example: "<p>Introducing the Premium Blue Widget...</p>"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /products/bulk:
    post:
      operationId: bulkCreateProducts
      summary: Bulk create products
      description: |
        Creates up to 10,000 products in a single request. Products are processed
        asynchronously and synced to Shopify in the background.
      tags: [Products]
      x-scopes: ["products:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductBulkCreateRequest"
      responses:
        "202":
          description: Bulk creation accepted and queued for processing.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: "Bulk product creation queued. 150 products will be processed."
                      jobId:
                        type: string
                        example: "65a1b2c3d4e5f67890abcdef"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Fulfillments ──────────────────────────────────────────────────────
  /fulfillments:
    get:
      operationId: listFulfillments
      summary: List fulfillments
      description: Returns a paginated list of fulfillment line items with filtering and sorting.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: fulfillmentStatus
          in: query
          description: Filter by fulfillment status.
          schema:
            type: string
            enum: [fulfilled, unfulfilled, on-hold, return-in-progress]
        - name: shipmentStatus
          in: query
          description: Filter by shipment tracking status.
          schema:
            type: string
            maxLength: 50
        - name: paymentStatus
          in: query
          description: Filter by payout/payment status.
          schema:
            type: string
            maxLength: 50
        - name: dateMin
          in: query
          schema:
            type: string
            format: date-time
        - name: dateMax
          in: query
          schema:
            type: string
            format: date-time
        - $ref: "#/components/parameters/SearchQuery"
        - name: sortBy
          in: query
          schema:
            type: string
            enum: [orderNumber, date, lineItemName, shipDate, deliveryDate]
            default: orderNumber
        - name: sortOrder
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        "200":
          description: Paginated fulfillment list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/FulfillmentNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createFulfillment
      summary: Create a fulfillment
      description: Creates a fulfillment for one or more line items, optionally with tracking information.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentCreateRequest"
      responses:
        "201":
          description: Fulfillment created.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/FulfillmentNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/counts:
    get:
      operationId: getFulfillmentCounts
      summary: Get fulfillment counts by status
      description: Returns fulfillment counts grouped by status.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      responses:
        "200":
          description: Fulfillment counts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FulfillmentCountsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/stats:
    get:
      operationId: getFulfillmentStats
      summary: Get fulfillment statistics
      description: Returns aggregated fulfillment statistics.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      responses:
        "200":
          description: Fulfillment statistics.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/{fulfillmentId}:
    put:
      operationId: updateFulfillment
      summary: Update fulfillment tracking
      description: Updates tracking information on an existing fulfillment.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      parameters:
        - name: fulfillmentId
          in: path
          required: true
          description: Fulfillment document ID.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentUpdateRequest"
      responses:
        "200":
          description: Fulfillment updated.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/FulfillmentNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/export:
    get:
      operationId: exportFulfillments
      summary: Export fulfillments as CSV
      description: Exports fulfillment data as a CSV file.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/events/export:
    get:
      operationId: exportFulfillmentEvents
      summary: Export fulfillment events as CSV
      description: Exports fulfillment event data as a CSV file.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/import:
    post:
      operationId: importFulfillments
      summary: Import fulfillments from CSV
      description: Uploads a CSV file to bulk-create fulfillments.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV file containing fulfillment data.
      responses:
        "202":
          description: Import accepted and queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: "Import queued for processing."
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/bulk:
    post:
      operationId: bulkCreateFulfillments
      summary: Bulk create fulfillments from CSV URL
      description: Processes fulfillments from a CSV file at the provided URL.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentBulkCsvRequest"
      responses:
        "202":
          description: Bulk fulfillment queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: "Bulk fulfillment queued for processing."
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/events/{lineItemId}:
    get:
      operationId: listFulfillmentEvents
      summary: List fulfillment events for a line item
      description: Returns all fulfillment tracking events for a specific line item.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:read"]
      parameters:
        - name: lineItemId
          in: path
          required: true
          description: Line item document ID.
          schema:
            type: string
      responses:
        "200":
          description: Fulfillment events.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                        status:
                          type: string
                        message:
                          type: string
                        occurredAt:
                          type: string
                          format: date-time
                        location:
                          type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createFulfillmentEvent
      summary: Create a fulfillment event
      description: Adds a tracking event to a line item's fulfillment history.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      parameters:
        - name: lineItemId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentEventCreateRequest"
      responses:
        "201":
          description: Event created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/events/{lineItemId}/{eventId}:
    put:
      operationId: updateFulfillmentEvent
      summary: Update a fulfillment event
      description: Updates an existing fulfillment tracking event.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      parameters:
        - name: lineItemId
          in: path
          required: true
          schema:
            type: string
        - name: eventId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentEventUpdateRequest"
      responses:
        "200":
          description: Event updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /fulfillments/events/bulk:
    post:
      operationId: bulkCreateFulfillmentEvents
      summary: Bulk create fulfillment events
      description: Creates up to 100 fulfillment tracking events across multiple line items in a single request.
      tags: [Fulfillments]
      x-scopes: ["fulfillments:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentEventBulkRequest"
      responses:
        "201":
          description: Events created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      created:
                        type: number
                        example: 15
                      errors:
                        type: array
                        items:
                          type: object
                          additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Documents ─────────────────────────────────────────────────────────
  /documents/invoice:
    post:
      operationId: generateInvoice
      summary: Generate invoice PDF
      description: Generates a vendor invoice document for the specified orders.
      tags: [Documents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentOrderRequest"
      responses:
        "200":
          description: Invoice generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /documents/packing-slip:
    post:
      operationId: generatePackingSlip
      summary: Generate packing slip PDF
      description: Generates a packing slip document for the specified orders and optional line items.
      tags: [Documents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentPackingSlipRequest"
      responses:
        "200":
          description: Packing slip generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /documents/shipping-label:
    post:
      operationId: generateShippingLabel
      summary: Generate shipping label PDF
      description: Generates a shipping label document for the specified orders and line items.
      tags: [Documents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentShippingLabelRequest"
      responses:
        "200":
          description: Shipping label generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /documents/customer-invoice:
    post:
      operationId: generateCustomerInvoice
      summary: Generate customer invoice PDF
      description: Generates a customer-facing invoice for the specified orders.
      tags: [Documents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentCustomerInvoiceRequest"
      responses:
        "200":
          description: Customer invoice generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /documents/grouped-invoice:
    post:
      operationId: generateGroupedInvoice
      summary: Generate grouped invoice PDF
      description: Generates a grouped invoice spanning multiple vendors or a date range.
      tags: [Documents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentGroupedInvoiceRequest"
      responses:
        "200":
          description: Grouped invoice generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Payouts ───────────────────────────────────────────────────────────
  /payouts:
    get:
      operationId: listPayouts
      summary: List payouts
      description: Returns a paginated list of payouts with filtering by status, fulfillment, and date range.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: status
          in: query
          schema:
            type: string
            enum: [paid, partial, unpaid]
        - name: fulfillment
          in: query
          schema:
            type: string
            enum: [fulfilled, unfulfilled]
        - $ref: "#/components/parameters/DateMinQuery"
        - $ref: "#/components/parameters/DateMaxQuery"
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: Paginated payout list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/PayoutNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/{payoutId}:
    get:
      operationId: getPayout
      summary: Get payout details
      description: Returns details for a specific payout including line item breakdown.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - name: payoutId
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/VendorIdQuery"
      responses:
        "200":
          description: Payout details.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/PayoutNode"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/summary:
    get:
      operationId: getPayoutSummary
      summary: Get payout summary
      description: Returns a high-level summary of payout totals, the most recent payout, and the next scheduled payout.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - $ref: "#/components/parameters/VendorIdQuery"
        - $ref: "#/components/parameters/DateMinQuery"
        - $ref: "#/components/parameters/DateMaxQuery"
      responses:
        "200":
          description: Payout summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayoutSummaryResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/transactions:
    get:
      operationId: listPayoutTransactions
      summary: List payout transactions
      description: Returns a paginated list of payout transactions (individual payment attempts).
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - name: vendorId
          in: query
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, paid, failed, cancelled]
        - $ref: "#/components/parameters/DateMinQuery"
        - $ref: "#/components/parameters/DateMaxQuery"
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: Paginated transaction list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/TransactionNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/batches/{batchId}:
    get:
      operationId: getPayoutBatch
      summary: Get payout batch details
      description: Returns details for a specific payout batch.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - name: batchId
          in: path
          required: true
          schema:
            type: string
        - name: vendorId
          in: query
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
      responses:
        "200":
          description: Batch details.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/pending:
    get:
      operationId: listPendingPayouts
      summary: List pending payout items
      description: Returns line items that are pending payout. Uses offset-based pagination.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      parameters:
        - name: vendorId
          in: query
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
        - $ref: "#/components/parameters/DateMinQuery"
        - $ref: "#/components/parameters/DateMaxQuery"
        - $ref: "#/components/parameters/SearchQuery"
        - name: lineItemType
          in: query
          description: Filter by line item type.
          schema:
            type: string
            enum: [paid, refunded, adjustment]
        - name: limit
          in: query
          description: Number of items per page (1-500).
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
        - name: offset
          in: query
          description: Number of items to skip.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Pending payout items.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/pending/export:
    get:
      operationId: exportPendingPayouts
      summary: Export pending payouts as CSV
      description: Exports pending payout items as a CSV file.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /payouts/transactions/export:
    get:
      operationId: exportPayoutTransactions
      summary: Export payout transactions as CSV
      description: Exports payout transaction history as a CSV file.
      tags: [Payouts]
      x-scopes: ["payouts:read"]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Dashboard ─────────────────────────────────────────────────────────
  /dashboard:
    get:
      operationId: getDashboard
      summary: Get dashboard metrics
      description: Returns vendor dashboard metrics including sales, orders, and performance data.
      tags: [Dashboard]
      responses:
        "200":
          description: Dashboard metrics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /dashboard/enhanced:
    get:
      operationId: getDashboardEnhanced
      summary: Get enhanced dashboard metrics
      description: Returns enhanced dashboard metrics with additional detail and breakdowns.
      tags: [Dashboard]
      responses:
        "200":
          description: Enhanced dashboard metrics.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /dashboard/chart:
    get:
      operationId: getDashboardChart
      summary: Get dashboard chart data
      description: Returns time-series data for dashboard charts.
      tags: [Dashboard]
      responses:
        "200":
          description: Chart data.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /dashboard/products/top:
    get:
      operationId: getTopProducts
      summary: Get top products
      description: Returns the vendor's top-performing products by sales.
      tags: [Dashboard]
      responses:
        "200":
          description: Top products.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /dashboard/products/top/export:
    get:
      operationId: exportTopProducts
      summary: Export top products as CSV
      description: Exports top product performance data as a CSV file.
      tags: [Dashboard]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Customers ─────────────────────────────────────────────────────────
  /customers:
    get:
      operationId: listCustomers
      summary: List customers
      description: Returns a paginated list of customers who have ordered from the vendor.
      tags: [Customers]
      x-scopes: ["customers:read"]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: createdAtMin
          in: query
          schema:
            type: string
            format: date-time
        - name: createdAtMax
          in: query
          schema:
            type: string
            format: date-time
        - name: locationId
          in: query
          schema:
            type: number
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: Paginated customer list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/CustomerNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  /customers/export:
    get:
      operationId: exportCustomers
      summary: Export customers as CSV
      description: Exports customer data as a CSV file.
      tags: [Customers]
      x-scopes: ["customers:read"]
      responses:
        "200":
          description: CSV file.
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Discounts ─────────────────────────────────────────────────────────
  /discounts:
    get:
      operationId: listDiscounts
      summary: List discounts
      description: Returns a paginated list of vendor discount codes.
      tags: [Discounts]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: status
          in: query
          schema:
            type: string
            enum: [active, expired, disabled]
        - $ref: "#/components/parameters/SearchQuery"
        - name: sortBy
          in: query
          schema:
            type: string
            enum: [code, title, status, usageCount, createdAt, updatedAt]
            default: updatedAt
        - name: sortOrder
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        "200":
          description: Paginated discount list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              $ref: "#/components/schemas/DiscountNode"
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createDiscount
      summary: Create a discount code
      description: Creates a new vendor discount code.
      tags: [Discounts]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DiscountCreateRequest"
      responses:
        "201":
          description: Discount created.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/DiscountNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /discounts/{discountId}:
    put:
      operationId: updateDiscount
      summary: Update a discount code
      description: Updates an existing discount code.
      tags: [Discounts]
      parameters:
        - name: discountId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DiscountUpdateRequest"
      responses:
        "200":
          description: Discount updated.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: "#/components/schemas/DiscountNode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteDiscount
      summary: Delete a discount code
      description: Deletes a discount code.
      tags: [Discounts]
      parameters:
        - name: discountId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Discount deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /discounts/collection-health:
    get:
      operationId: getDiscountCollectionHealth
      summary: Check discount collection health
      description: Verifies that vendor collections required for discount functionality are properly configured.
      tags: [Discounts]
      responses:
        "200":
          description: Collection health status.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Commissions ───────────────────────────────────────────────────────
  /commissions/vendor:
    get:
      operationId: getVendorCommissions
      summary: Get vendor commission rates
      description: Returns the commission rates configured for the vendor.
      tags: [Commissions]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
      responses:
        "200":
          description: Vendor commission rates.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /commissions/sku:
    get:
      operationId: listSkuCommissions
      summary: List SKU commission rates
      description: Returns SKU-level commission rate overrides.
      tags: [Commissions]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - name: sku
          in: query
          description: Filter by exact SKU.
          schema:
            type: string
        - name: productId
          in: query
          description: Filter by Shopify product ID.
          schema:
            type: number
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: SKU commission rates.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: setSkuCommissions
      summary: Set SKU commission rates
      description: Creates or updates SKU-level commission rate overrides (up to 500 per request).
      tags: [Commissions]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SkuCommissionInput"
      responses:
        "200":
          description: SKU commissions updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      updated:
                        type: number
                        example: 15
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Shipping ──────────────────────────────────────────────────────────
  /shipping/get-rates:
    post:
      operationId: getShippingRates
      summary: Get shipping rates
      description: Fetches real-time shipping rate quotes from configured carriers based on package dimensions, weight, and addresses.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GetRatesRequest"
      responses:
        "200":
          description: Shipping rates.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        carrier:
                          type: string
                          example: "USPS"
                        service:
                          type: string
                          example: "Priority Mail"
                        service_code:
                          type: string
                          example: "usps_priority"
                        amount:
                          type: number
                          example: 7.99
                        currency:
                          type: string
                          example: "USD"
                        estimatedDays:
                          type: number
                          example: 3
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/purchase-label:
    post:
      operationId: purchaseShippingLabel
      summary: Purchase a shipping label
      description: Purchases a shipping label for a selected rate. Extends the get-rates request with the selected service code and carrier.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PurchaseLabelRequest"
      responses:
        "200":
          description: Label purchased.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      labelUrl:
                        type: string
                        format: uri
                      trackingNumber:
                        type: string
                      carrier:
                        type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/generate-label:
    post:
      operationId: generateShippingLabel
      summary: Generate a shipping label
      description: Generates a shipping label via the vendor's configured shipping provider. Additional fields beyond vendorId are passed through to the provider.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateLabelRequest"
      responses:
        "200":
          description: Label generated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/dimensions:
    get:
      operationId: listShippingDimensions
      summary: List shipping dimension presets
      description: Returns saved shipping dimension presets, optionally filtered by type or product.
      tags: [Shipping]
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [generic, productSpecific]
        - name: productId
          in: query
          schema:
            type: string
        - name: title
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Dimension presets.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createShippingDimension
      summary: Create a shipping dimension preset
      description: Creates a new shipping dimension preset for quick selection during label creation.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShippingDimensionCreateRequest"
      responses:
        "201":
          description: Dimension preset created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/dimensions/{shippingDimensionsId}:
    put:
      operationId: updateShippingDimension
      summary: Update a shipping dimension preset
      description: Updates an existing shipping dimension preset.
      tags: [Shipping]
      parameters:
        - name: shippingDimensionsId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShippingDimensionUpdateRequest"
      responses:
        "200":
          description: Dimension preset updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteShippingDimension
      summary: Delete a shipping dimension preset
      description: Deletes a shipping dimension preset.
      tags: [Shipping]
      parameters:
        - name: shippingDimensionsId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Preset deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/from-addresses:
    get:
      operationId: listFromAddresses
      summary: List saved from-addresses
      description: Returns saved shipping origin addresses.
      tags: [Shipping]
      responses:
        "200":
          description: From addresses.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/validate-address:
    post:
      operationId: validateAddress
      summary: Validate a shipping address
      description: Validates a shipping address via the configured shipping provider.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateAddressRequest"
      responses:
        "200":
          description: Address validation result.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      valid:
                        type: boolean
                        example: true
                      suggestedAddress:
                        type: object
                        additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping-carriers:
    get:
      operationId: listShippingCarriers
      summary: List available shipping carriers
      description: Returns the list of shipping carriers available to the vendor.
      tags: [Shipping]
      responses:
        "200":
          description: Available carriers.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          example: "USPS"
                        code:
                          type: string
                          example: "usps"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/variant-weights:
    post:
      operationId: calculateVariantWeights
      summary: Calculate total weight for variants
      description: Calculates the total weight for given variant quantities, converted to the specified target unit.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VariantWeightsRequest"
      responses:
        "200":
          description: Weight calculation result.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      totalWeight:
                        type: number
                        example: 3.5
                      unit:
                        type: string
                        example: "lb"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shipping/unfulfilled-quantity:
    post:
      operationId: getUnfulfilledQuantity
      summary: Get unfulfilled quantity for line items
      description: Returns the unfulfilled quantity remaining for each specified line item.
      tags: [Shipping]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UnfulfilledQuantityRequest"
      responses:
        "200":
          description: Unfulfilled quantities.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Vendor Profile ────────────────────────────────────────────────────
  /vendors/me/profile:
    get:
      operationId: getVendorProfile
      summary: Get vendor profile
      description: Returns the authenticated vendor's profile information.
      tags: [Profile]
      responses:
        "200":
          description: Vendor profile.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateVendorProfile
      summary: Update vendor profile
      description: Updates specific fields on the vendor profile using a field-value pairs format.
      tags: [Profile]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VendorProfileUpdateRequest"
      responses:
        "200":
          description: Profile updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /vendors/me/address:
    get:
      operationId: getVendorAddress
      summary: Get vendor address
      description: Returns the authenticated vendor's business address.
      tags: [Profile]
      responses:
        "200":
          description: Vendor address.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      address1:
                        type: string
                      address2:
                        type: string
                      city:
                        type: string
                      state:
                        type: string
                      country:
                        type: string
                      zip:
                        type: string
                      phone:
                        type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateVendorAddress
      summary: Update vendor address
      description: Updates the vendor's business address.
      tags: [Profile]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VendorAddressUpdateRequest"
      responses:
        "200":
          description: Address updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Account Settings ──────────────────────────────────────────────────
  /settings/accounts:
    get:
      operationId: getAccountSettings
      summary: Get account settings
      description: Returns the authenticated user's account settings including email, profile, and login history.
      tags: [Settings]
      responses:
        "200":
          description: Account settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountSettingsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateAccountSettings
      summary: Update account settings
      description: |
        Updates account settings including name, email, and password.
        To change the password, you must provide `currentPassword`, `password`, and `confirmPassword`.
      tags: [Settings]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountSettingsUpdateRequest"
      responses:
        "200":
          description: Account updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/profiles:
    get:
      operationId: getProfileSettings
      summary: Get profile settings
      description: Returns the vendor's structured profile settings including business info, social media, policies, and vacation mode.
      tags: [Settings]
      responses:
        "200":
          description: Profile settings.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateProfileSettings
      summary: Update profile settings
      description: Updates the vendor's structured profile settings. Only include the sections you want to change.
      tags: [Settings]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileSettingsUpdateRequest"
      responses:
        "200":
          description: Profile settings updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Shipping Settings ─────────────────────────────────────────────────
  /settings/shipping/rates:
    get:
      operationId: getShippingRates
      summary: Get shipping rate configuration
      description: Returns the vendor's current shipping rate method and configuration.
      tags: [Shipping Settings]
      responses:
        "200":
          description: Shipping rate config.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateShippingRates
      summary: Update shipping rate configuration
      description: |
        Updates the vendor's shipping rate method. The request body shape depends on
        the selected method (order, lineItem, orderRange, orderWeight, or realtime).
      tags: [Shipping Settings]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShippingRateUpdateRequest"
      responses:
        "200":
          description: Shipping rates updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/shipping/warehouses:
    get:
      operationId: listWarehouses
      summary: List warehouses
      description: Returns all warehouses configured for the vendor.
      tags: [Shipping Settings]
      responses:
        "200":
          description: Warehouse list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createWarehouse
      summary: Create a warehouse
      description: Creates a new warehouse/shipping origin for the vendor.
      tags: [Shipping Settings]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WarehouseCreateRequest"
      responses:
        "201":
          description: Warehouse created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/shipping/warehouses/{warehouseId}:
    get:
      operationId: getWarehouse
      summary: Get warehouse details
      description: Returns details for a specific warehouse.
      tags: [Shipping Settings]
      parameters:
        - name: warehouseId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Warehouse details.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateWarehouse
      summary: Update a warehouse
      description: Updates an existing warehouse. Only include fields you want to change.
      tags: [Shipping Settings]
      parameters:
        - name: warehouseId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WarehouseUpdateRequest"
      responses:
        "200":
          description: Warehouse updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteWarehouse
      summary: Delete a warehouse
      description: Deletes a warehouse.
      tags: [Shipping Settings]
      parameters:
        - name: warehouseId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Warehouse deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Team ──────────────────────────────────────────────────────────────
  /settings/team:
    get:
      operationId: listTeamMembers
      summary: List team members
      description: Returns a paginated list of team members for the vendor account.
      tags: [Team]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: Team member list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              type: object
                              additionalProperties: true
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createTeamMember
      summary: Add a team member
      description: Creates a new team member for the vendor account.
      tags: [Team]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TeamMemberCreateRequest"
      responses:
        "201":
          description: Team member created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/team/{userId}:
    put:
      operationId: updateTeamMember
      summary: Update a team member
      description: Updates an existing team member's details and permissions.
      tags: [Team]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TeamMemberUpdateRequest"
      responses:
        "200":
          description: Team member updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteTeamMember
      summary: Remove a team member
      description: Removes a team member from the vendor account.
      tags: [Team]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Team member removed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Users ─────────────────────────────────────────────────────────────
  /users:
    get:
      operationId: listUsers
      summary: List users
      description: Returns a paginated list of users with filtering by type, approval status, and vendor.
      tags: [Users]
      parameters:
        - $ref: "#/components/parameters/FirstParam"
        - $ref: "#/components/parameters/AfterParam"
        - $ref: "#/components/parameters/LastParam"
        - $ref: "#/components/parameters/BeforeParam"
        - $ref: "#/components/parameters/VendorIdQuery"
        - name: type
          in: query
          description: Filter by user type.
          schema:
            type: string
            enum: [vendor, staff, merchant, admin]
        - name: approved
          in: query
          description: Filter by approval status.
          schema:
            type: boolean
        - $ref: "#/components/parameters/SearchQuery"
      responses:
        "200":
          description: Paginated user list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    required: [edges, pageInfo, totalCount]
                    properties:
                      edges:
                        type: array
                        items:
                          type: object
                          properties:
                            node:
                              type: object
                              additionalProperties: true
                            cursor:
                              type: string
                      pageInfo:
                        $ref: "#/components/schemas/PageInfo"
                      totalCount:
                        type: number
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createUser
      summary: Create a user
      description: Creates a new user account, optionally sending an invitation email.
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserCreateRequest"
      responses:
        "201":
          description: User created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /users/{userId}:
    put:
      operationId: updateUser
      summary: Update a user
      description: Updates an existing user's account details.
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserUpdateRequest"
      responses:
        "200":
          description: User updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteUser
      summary: Delete a user
      description: Deletes a user account.
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: User deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Integrations ──────────────────────────────────────────────────────
  /integrations:
    get:
      operationId: listIntegrations
      summary: List integrations
      description: Returns a grouped list of all available integrations and their configuration status.
      tags: [Integrations]
      responses:
        "200":
          description: Integration list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /integrations/paypal:
    get:
      operationId: getPaypalIntegration
      summary: Get PayPal integration status
      description: Returns the PayPal integration configuration status and masked email.
      tags: [Integrations]
      responses:
        "200":
          description: PayPal status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaypalResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updatePaypalIntegration
      summary: Configure PayPal integration
      description: Sets the PayPal email address for receiving payouts.
      tags: [Integrations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaypalUpdateRequest"
      responses:
        "200":
          description: PayPal configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaypalResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deletePaypalIntegration
      summary: Remove PayPal integration
      description: Removes the PayPal integration configuration.
      tags: [Integrations]
      responses:
        "200":
          description: PayPal removed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /integrations/stripe:
    get:
      operationId: getStripeIntegration
      summary: Get Stripe integration status
      description: Returns the Stripe Connect integration status and account details.
      tags: [Integrations]
      responses:
        "200":
          description: Stripe status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StripeStatusResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteStripeIntegration
      summary: Disconnect Stripe integration
      description: Disconnects the Stripe Connect account.
      tags: [Integrations]
      responses:
        "200":
          description: Stripe disconnected.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /integrations/stripe/connect:
    post:
      operationId: connectStripe
      summary: Start Stripe Connect onboarding
      description: Initiates Stripe Connect onboarding and returns the URL to redirect the vendor to.
      tags: [Integrations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StripeConnectRequest"
      responses:
        "200":
          description: Onboarding URL generated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StripeConnectResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /integrations/shippo:
    get:
      operationId: getShippoIntegration
      summary: Get Shippo integration status
      description: Returns the Shippo integration configuration status.
      tags: [Integrations]
      responses:
        "200":
          description: Shippo status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShippingProviderResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateShippoIntegration
      summary: Configure Shippo integration
      description: Sets the Shippo API key.
      tags: [Integrations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShippingProviderUpdateRequest"
      responses:
        "200":
          description: Shippo configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShippingProviderResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteShippoIntegration
      summary: Remove Shippo integration
      description: Removes the Shippo integration.
      tags: [Integrations]
      responses:
        "200":
          description: Shippo removed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /integrations/shipstation:
    get:
      operationId: getShipstationIntegration
      summary: Get ShipStation integration status
      description: Returns the ShipStation integration configuration status.
      tags: [Integrations]
      responses:
        "200":
          description: ShipStation status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShippingProviderResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    put:
      operationId: updateShipstationIntegration
      summary: Configure ShipStation integration
      description: Sets the ShipStation API key.
      tags: [Integrations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShippingProviderUpdateRequest"
      responses:
        "200":
          description: ShipStation configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShippingProviderResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    delete:
      operationId: deleteShipstationIntegration
      summary: Remove ShipStation integration
      description: Removes the ShipStation integration.
      tags: [Integrations]
      responses:
        "200":
          description: ShipStation removed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── API Keys ──────────────────────────────────────────────────────────
  /settings/public-api/keys:
    get:
      operationId: listApiKeys
      summary: List API keys
      description: Returns all API keys for the vendor, including revoked keys.
      tags: [API Keys]
      responses:
        "200":
          description: API key list.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKeyNode"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createApiKey
      summary: Create an API key
      description: |
        Creates a new API key with the specified label, mode, and scopes.
        The full token is returned **only once** in the response. Store it securely.
      tags: [API Keys]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiKeyCreateRequest"
      responses:
        "201":
          description: API key created. The `token` field is shown only once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyCreateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/public-api/keys/{keyId}:
    delete:
      operationId: revokeApiKey
      summary: Revoke an API key
      description: Permanently revokes an API key. The key can no longer be used for authentication.
      tags: [API Keys]
      parameters:
        - name: keyId
          in: path
          required: true
          description: API key document ID (24-character hex string).
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
      responses:
        "200":
          description: API key revoked.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  /settings/public-api/keys/{keyId}/rotate:
    post:
      operationId: rotateApiKey
      summary: Rotate an API key
      description: |
        Generates a new token for the API key, immediately invalidating the old one.
        The new token is returned **only once**. Store it securely.
      tags: [API Keys]
      parameters:
        - name: keyId
          in: path
          required: true
          description: API key document ID (24-character hex string).
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{24}$"
      responses:
        "200":
          description: Key rotated. The new `token` is shown only once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyRotateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"

  # ── Shop ──────────────────────────────────────────────────────────────
  /shop/config/signup:
    get:
      operationId: getShopSignupConfig
      summary: Get shop signup configuration
      description: Returns the public signup form configuration for the shop. No authentication required.
      tags: [Shop]
      security: []
      x-rate-limit: "20 requests per 15 minutes per IP"
      responses:
        "200":
          description: Signup form configuration.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    description: Shop signup form fields, branding, and settings.
                    additionalProperties: true
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /shop/product-config:
    get:
      operationId: getShopProductConfig
      summary: Get product form configuration
      description: Returns the product creation/editing form configuration, including available fields, categories, and validation rules.
      tags: [Shop]
      responses:
        "200":
          description: Product form configuration.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    description: Product form fields, categories, and allowed options.
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /shop/features:
    get:
      operationId: getShopFeatures
      summary: Get shop feature flags
      description: Returns the feature flags and capabilities enabled for the shop.
      tags: [Shop]
      responses:
        "200":
          description: Feature flags.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    description: Feature flags and their enabled/disabled status.
                    additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
