openapi: 3.1.0
info:
  title: LandedFees API
  version: "1.0.0"
  summary: Landed-cost, HS classification, and origin-comparison API.
  description: |
    LandedFees exposes five stable v1 endpoints for landed-cost calculation,
    HS classification, multi-origin comparison, and rate lookup. All requests
    require a bearer API key (format `sk_live_...`). Every response returns
    JSON. Rate limits are surfaced via `X-RateLimit-*` headers.

    Data provenance: rates resolve against the same live tables the
    LandedFees UI uses (USITC HTS, EU TARIC, HMRC UK Trade Tariff, WCO HS,
    CBP weekly FX per 19 CFR 141.62). No estimates.

    Idempotency: mutation-like endpoints (calc, calc/bulk, classify,
    compare) accept an `Idempotency-Key` header. Repeating the same key
    within 24 hours returns the cached response.
  contact:
    name: LandedFees Support
    url: https://www.landedfees.com/support
    email: support@landedfees.com
  license:
    name: Proprietary
    url: https://www.landedfees.com/terms
servers:
  - url: https://www.landedfees.com
    description: Production (v1)

tags:
  - name: Calculate
    description: Landed-cost calculation for single and bulk shipments.
  - name: Classify
    description: HS / HTS code classification from a product description.
  - name: Compare
    description: Side-by-side landed-cost comparison across origins.
  - name: Rates
    description: Duty and tax rate lookup by country and HS code.

security:
  - bearerAuth: []

paths:
  /v1/calc:
    post:
      tags: [Calculate]
      operationId: calc
      summary: Calculate landed cost for one shipment.
      description: |
        Runs the landed-cost engine end-to-end for a single shipment. Resolves
        MFN, Section 232 / 301 / 122 / 338, AD/CVD, FTA preferences, VAT/GST,
        MPF, HMF, and de-minimis. Returns a full CalculationResult plus
        compliance flags (PGA, restricted product, denied party, unit-price
        deviation, data-freshness, Section 122 sunset banner).
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalcRequest'
            examples:
              earbuds_cn_to_us:
                summary: Wireless earbuds CN to US, single line
                value:
                  destination_country: US
                  origin_country: CN
                  incoterm: FOB
                  transport_mode: ocean
                  currency: USD
                  freight: 250
                  insurance: 45
                  line_items:
                    - description: Wireless earbuds, plastic housing
                      hs_code: "8517.62.00"
                      quantity: 500
                      unit_value: 12.5
                      origin_country: CN
                      weight_kg: 0.15
      responses:
        '200':
          description: Calculation completed.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalcResponse'
              examples:
                ok:
                  summary: Success
                  value:
                    id: "5f6b7c8d-1234-4abc-9def-0123456789ab"
                    result:
                      goods_value: 6250
                      freight: 250
                      insurance: 45
                      customs_value: 6545
                      duty_amount: 3796.1
                      taxes_amount: 0
                      fees_amount: 25.67
                      total_landed_cost: 10366.77
                      currency: USD
                      calculation_date: "2026-08-23"
                      breakdown:
                        - code: MFN
                          amount: 0
                          rate: 0
                          exempted: false
                        - code: SEC_301
                          amount: 1636.25
                          rate: 0.25
                          exempted: false
                        - code: SEC_232
                          amount: 0
                          rate: 0
                          exempted: false
                    compliance_flags: []
                    data_stale: false
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '422': { $ref: '#/components/responses/UnprocessableEntity' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

  /v1/calc/bulk:
    post:
      tags: [Calculate]
      operationId: calcBulk
      summary: Bulk landed-cost calculation (up to 500 rows).
      description: |
        Accepts up to 500 single-line rows sharing ship-level fields
        (destination, currency, incoterm, transport_mode, freight,
        insurance). Freight and insurance are pro-rated across rows by
        line value. Row-level validation errors do not fail the batch;
        they are returned in the `errors` array alongside successful
        rows in `results`.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalcBulkRequest'
            examples:
              mixed_sku:
                summary: 3-row mixed-HS shipment CN to US
                value:
                  destination_country: US
                  currency: USD
                  incoterm: FOB
                  transport_mode: ocean
                  freight: 1200
                  insurance: 180
                  rows:
                    - description: Bluetooth earbuds
                      hs_code: "8517.62"
                      quantity: 500
                      unit_value: 12.5
                      origin_country: CN
                    - description: USB-C charging cable
                      hs_code: "8544.42"
                      quantity: 1000
                      unit_value: 1.2
                      origin_country: CN
                    - description: Silicone phone case
                      hs_code: "3926.90"
                      quantity: 800
                      unit_value: 2.0
                      origin_country: VN
      responses:
        '200':
          description: Bulk calculation completed.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalcBulkResponse'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

  /v1/classify:
    post:
      tags: [Classify]
      operationId: classify
      summary: Classify a product description to an HS / HTS code.
      description: |
        Accepts either a single description or a batch of up to 50 lines.
        Returns the recommended 6-digit HS code, a 10-digit HTS suggestion
        when available, and a confidence score. Optional
        `hs_code_printed` lets you pass the code your supplier printed on
        the commercial invoice; the classifier compares it and flags
        disagreements.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ClassifySingleRequest'
                - $ref: '#/components/schemas/ClassifyBatchRequest'
            examples:
              single:
                summary: Single product description
                value:
                  description: Wireless bluetooth earbuds with charging case
                  destination_country: US
              batch:
                summary: Batch of 3 lines
                value:
                  destination_country: US
                  lines:
                    - description: USB-C charging cable
                    - description: Silicone phone case
                      hs_code_printed: "3926.90"
                    - description: Bluetooth earbuds
      responses:
        '200':
          description: Classification completed.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ClassificationResult'
                  - $ref: '#/components/schemas/BatchClassificationResult'
              examples:
                single:
                  summary: Single-line result
                  value:
                    hs_6_code: "851762"
                    recommended_10_digit: "8517.62.00.90"
                    confidence: 0.92
                    reason: "Wireless communication apparatus, other reception apparatus"
                    needs_review: false
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

  /v1/compare:
    post:
      tags: [Compare]
      operationId: compare
      summary: Compare landed cost across up to 6 origin countries.
      description: |
        Runs the landed-cost engine in parallel for 1 to 6 origin
        countries against a single HS + destination + value + freight
        combination. Returns a compact per-origin breakdown (MFN,
        Section 122 / 301 / 301-FL / 232 / 338, AD/CVD, MPF, HMF, VAT)
        plus flags (denied-party, PGA, restriction, AD/CVD,
        fta_opportunity). Per-origin failures do not fail the batch.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompareRequest'
            examples:
              cn_vn_mx_in:
                summary: 4-origin comparison for HS 8518.30
                value:
                  hs_code: "8518.30.20"
                  destination: US
                  value: 64250
                  currency: USD
                  freight: 3200
                  insurance: 480
                  incoterm: FOB
                  transport_mode: ocean
                  origins: [CN, VN, MX, IN]
      responses:
        '200':
          description: Comparison completed.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompareResponse'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

  /v1/rates/{country}/{hs}:
    get:
      tags: [Rates]
      operationId: rates
      summary: Look up duty and tax rates for a country + HS code.
      description: |
        Returns the resolved MFN rate plus every applicable overlay
        (Section 232 / 301 / 122 / 301-FL / 338, AD/CVD, preferential
        FTA rates by program) and destination-country VAT / GST. Read-only,
        cacheable for 1 hour.
      parameters:
        - name: country
          in: path
          required: true
          description: ISO-3166-1 alpha-2 destination country code (e.g. US, DE, GB).
          schema:
            type: string
            minLength: 2
            maxLength: 2
            example: US
        - name: hs
          in: path
          required: true
          description: HS or HTS code, 4 to 10 digits (dots optional).
          schema:
            type: string
            minLength: 4
            maxLength: 15
            example: "8517.62.00"
        - name: origin
          in: query
          required: false
          description: Origin country for stacking overlays (Section 301, AD/CVD).
          schema:
            type: string
            minLength: 2
            maxLength: 2
            example: CN
        - name: calculation_date
          in: query
          required: false
          description: ISO date the rates are effective on. Defaults to today.
          schema:
            type: string
            format: date
            example: "2026-08-23"
      responses:
        '200':
          description: Rates resolved.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RatesResponse'
              examples:
                us_cn_8517:
                  summary: US import from CN, HS 8517.62.00
                  value:
                    country: US
                    hs_code: "8517.62.00"
                    origin: CN
                    calculation_date: "2026-08-23"
                    mfn_rate: 0
                    preferential_rate: null
                    overlays:
                      section_301: 0.25
                      section_122: 0.10
                      section_232: null
                      section_301_fl: null
                      section_338_ca: null
                      ad_cvd: null
                    vat_rate: null
                    sources:
                      - USITC HTS 2026
                      - USTR Section 301 List 4A
                    data_as_of: "2026-08-22T14:03:00Z"
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '404':
          description: No rate found for the given country + HS combination.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_live
      description: |
        API key issued from the LandedFees dashboard under Settings > API keys.
        Format: `sk_live_...` (production) or `sk_test_...` (sandbox). Pass in
        the `Authorization: Bearer <key>` header.

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Client-generated unique string (recommended UUID v4). Repeating the
        same key within 24 hours returns the cached response for the original
        request body. Different bodies with the same key return HTTP 409.
      schema:
        type: string
        minLength: 8
        maxLength: 128
        example: "e4c1d2a0-8f6b-4c9d-a123-2f8b7c5e9d10"

  headers:
    XRateLimitLimit:
      description: Total requests allowed in the current window.
      schema:
        type: integer
        example: 1000
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
        example: 987
    XRateLimitReset:
      description: Unix epoch seconds when the window resets.
      schema:
        type: integer
        example: 1755273600

  responses:
    BadRequest:
      description: Request body failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: invalid body
            issues:
              - path: [destination_country]
                message: String must contain exactly 2 character(s)
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: unauthorized
    PaymentRequired:
      description: Quota exhausted for the current plan. Upgrade or wait for the monthly reset.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: quota_exceeded
            code: QUOTA_EXCEEDED
            message: Monthly quota exhausted. Upgrade at https://www.landedfees.com/pricing.
    UnprocessableEntity:
      description: Semantic failure (missing HS code, unsupported destination, domestic shipment).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: missing_hs_code
            code: MISSING_HS_CODE
            message: Could not derive HS code for 1 line item(s).
    TooManyRequests:
      description: Rate limit exceeded. Retry after the reset window.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: rate_limited
            code: RATE_LIMITED
            message: Too many requests. Retry after 60 seconds.
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: internal
            detail: engine error

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
        code:
          type: string
        message:
          type: string
        detail:
          type: string
        issues:
          type: array
          items:
            type: object

    Incoterm:
      type: string
      enum: [EXW, FCA, CPT, CIP, DAP, DPU, DDP, FAS, FOB, CFR, CIF]

    TransportMode:
      type: string
      enum: [ocean, air, truck, rail, express]

    LineItem:
      type: object
      required: [description, quantity, unit_value, origin_country]
      properties:
        description:
          type: string
          minLength: 1
        hs_code:
          type: string
          description: HS / HTS code, 4 to 13 digits (dots allowed). Empty string triggers inline classification.
          example: "8517.62.00"
        quantity:
          type: number
          exclusiveMinimum: 0
        unit_value:
          type: number
          minimum: 0
        origin_country:
          type: string
          minLength: 2
          maxLength: 2
          example: CN
        weight_kg:
          type: number
          minimum: 0
          nullable: true
        manufacturer:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        smelter_country:
          type: string
          minLength: 2
          maxLength: 2
          nullable: true
          description: |
            ISO-2 smelter country for Chapter 76 aluminum (or Chapters 72/73/74
            metals). Triggers Section 232 Russia-aluminum 200 percent override
            when set to RU regardless of origin_country.
        preference_program:
          type: string
          nullable: true
          description: |
            Per-line preference program claim (USMCA, KORUS, CAFTA-DR, S, S+,
            P, P+, bilateral ISO-2s, etc.).

    CustomsValueAdjustments:
      type: object
      description: Itemized customs-value adjustments per 19 CFR 152.103.
      properties:
        assists: { type: number, minimum: 0 }
        royalties_license_fees: { type: number, minimum: 0 }
        selling_commission: { type: number, minimum: 0 }
        packing_costs: { type: number, minimum: 0 }
        proceeds_subsequent_resale: { type: number, minimum: 0 }
        buying_commission: { type: number, minimum: 0 }
        prompt_payment_discount: { type: number, minimum: 0 }

    CalcRequest:
      type: object
      required:
        - destination_country
        - origin_country
        - incoterm
        - transport_mode
        - currency
        - line_items
      properties:
        destination_country:
          type: string
          minLength: 2
          maxLength: 2
          example: US
        origin_country:
          type: string
          minLength: 2
          maxLength: 2
          example: CN
        incoterm: { $ref: '#/components/schemas/Incoterm' }
        transport_mode: { $ref: '#/components/schemas/TransportMode' }
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: USD
        line_items:
          type: array
          minItems: 1
          items: { $ref: '#/components/schemas/LineItem' }
        freight: { type: number, minimum: 0, default: 0 }
        insurance: { type: number, minimum: 0, default: 0 }
        other_dutiable_charges: { type: number, minimum: 0, default: 0 }
        customs_value_adjustments: { $ref: '#/components/schemas/CustomsValueAdjustments' }
        eu_member_state:
          type: string
          minLength: 2
          maxLength: 2
          description: Required when destination_country is EU.
        br_state: { type: string, minLength: 2, maxLength: 2 }
        ca_province: { type: string, minLength: 2, maxLength: 2 }
        usmca_qualifying: { type: boolean }
        fta_id:
          type: string
          description: FTA program ID (KORUS, CAFTA_DR, etc.).
        calculation_date: { type: string, format: date }
        export_date:
          type: string
          format: date
          description: |
            ISO date the shipment left the country of exportation. Drives the
            CBP-published weekly FX lookup per 19 CFR 141.62. Falls back to
            calculation_date when unset.
        input_method:
          type: string
          enum: [upload, manual]
          default: manual
        extracted_data:
          nullable: true
          description: Snapshot of extractor output for auto-detected fields.
        invoice_file_url:
          type: string
          nullable: true

    CalcBreakdownLayer:
      type: object
      required: [code, amount]
      properties:
        code:
          type: string
          description: |
            Duty / tax / fee code (MFN, SEC_122, SEC_301, SEC_301_FL,
            SEC_338_CA, SEC_232, SEC_232_PHARMA, AD_CVD, MPF, HMF, VAT,
            GST, GST_HST, IGST, BCD, II, IPI, ICMS, etc.).
        amount: { type: number }
        rate: { type: number, nullable: true }
        exempted: { type: boolean, default: false }
        note: { type: string, nullable: true }

    ComplianceFlag:
      type: object
      required: [kind, severity, title]
      properties:
        kind:
          type: string
          enum:
            - section_122
            - fx_rate
            - data_gap
            - restriction
            - pga
            - ad_cvd
            - fta_opportunity
            - fta_qualified
            - unit_price_deviation
        severity:
          type: string
          enum: [info, warning, critical]
        title: { type: string }
        detail: { type: string, nullable: true }
        source: { type: string, nullable: true }
        source_url: { type: string, format: uri, nullable: true }
        legal_citation: { type: string, nullable: true }
        hs_code: { type: string, nullable: true }
        restriction_type: { type: string, nullable: true }

    CalcResult:
      type: object
      required:
        - goods_value
        - freight
        - insurance
        - customs_value
        - duty_amount
        - taxes_amount
        - fees_amount
        - total_landed_cost
        - currency
        - calculation_date
        - breakdown
      properties:
        goods_value: { type: number }
        freight: { type: number }
        insurance: { type: number }
        customs_value: { type: number }
        duty_amount: { type: number }
        taxes_amount: { type: number }
        fees_amount: { type: number }
        total_landed_cost: { type: number }
        currency: { type: string }
        calculation_date: { type: string, format: date }
        breakdown:
          type: array
          items: { $ref: '#/components/schemas/CalcBreakdownLayer' }
        de_minimis:
          type: object
          properties:
            applies: { type: boolean }
            threshold: { type: number, nullable: true }
            currency: { type: string, nullable: true }
        warnings:
          type: array
          items: { type: string }

    CalcResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          nullable: true
        shipment_id:
          type: string
          format: uuid
          nullable: true
        result: { $ref: '#/components/schemas/CalcResult' }
        compliance_flags:
          type: array
          items: { $ref: '#/components/schemas/ComplianceFlag' }
        fta_opportunities:
          type: array
          items: { $ref: '#/components/schemas/ComplianceFlag' }
        data_as_of:
          type: object
          additionalProperties:
            type: string
            format: date-time
        data_stale: { type: boolean }

    CalcBulkRow:
      type: object
      required: [description, hs_code, quantity, unit_value, origin_country]
      properties:
        description: { type: string, minLength: 1 }
        hs_code:
          type: string
          pattern: '^[0-9.]{4,15}$'
          example: "8517.62"
        quantity: { type: number, exclusiveMinimum: 0 }
        unit_value: { type: number, minimum: 0 }
        origin_country: { type: string, minLength: 2, maxLength: 2 }
        weight_kg: { type: number, minimum: 0, nullable: true }

    CalcBulkRequest:
      type: object
      required:
        - destination_country
        - currency
        - incoterm
        - transport_mode
        - rows
      properties:
        destination_country: { type: string, minLength: 2, maxLength: 2 }
        currency: { type: string, minLength: 3, maxLength: 3 }
        incoterm: { $ref: '#/components/schemas/Incoterm' }
        transport_mode: { $ref: '#/components/schemas/TransportMode' }
        freight: { type: number, minimum: 0, default: 0 }
        insurance: { type: number, minimum: 0, default: 0 }
        eu_member_state: { type: string, minLength: 2, maxLength: 2 }
        br_state: { type: string, minLength: 2, maxLength: 2 }
        ca_province: { type: string, minLength: 2, maxLength: 2 }
        usmca_qualifying: { type: boolean }
        fta_id: { type: string }
        calculation_date: { type: string, format: date }
        rows:
          type: array
          minItems: 1
          maxItems: 500
          items: { $ref: '#/components/schemas/CalcBulkRow' }

    CalcBulkRowResult:
      type: object
      properties:
        line_index: { type: integer }
        ok: { type: boolean }
        description: { type: string }
        hs_code: { type: string }
        origin: { type: string }
        quantity: { type: number }
        unit_value: { type: number }
        line_value: { type: number }
        duty: { type: number }
        taxes: { type: number }
        fees: { type: number }
        landed_cost: { type: number }
        currency: { type: string }
        de_minimis_applied: { type: boolean }
        warnings:
          type: array
          items: { type: string }

    CalcBulkResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          nullable: true
        results:
          type: array
          items: { $ref: '#/components/schemas/CalcBulkRowResult' }
        errors:
          type: array
          items:
            type: object
            properties:
              line_index: { type: integer }
              reason: { type: string }
              field: { type: string }
        aggregate:
          type: object
          properties:
            total_rows: { type: integer }
            ok_rows: { type: integer }
            error_rows: { type: integer }
            total_value: { type: number }
            total_duty: { type: number }
            total_taxes: { type: number }
            total_fees: { type: number }
            total_landed: { type: number }
            currency: { type: string }

    ClassifySingleRequest:
      type: object
      required: [description, destination_country]
      properties:
        description: { type: string, minLength: 3, maxLength: 2000 }
        destination_country: { type: string, minLength: 2, maxLength: 2 }
        hs_code_printed: { type: string, maxLength: 64, nullable: true }
        confidence_threshold: { type: number, minimum: 0, maximum: 1 }

    ClassifyBatchLine:
      type: object
      required: [description]
      properties:
        description: { type: string, minLength: 3, maxLength: 2000 }
        hs_code_printed: { type: string, maxLength: 64, nullable: true }

    ClassifyBatchRequest:
      type: object
      required: [lines, destination_country]
      properties:
        lines:
          type: array
          minItems: 1
          maxItems: 50
          items: { $ref: '#/components/schemas/ClassifyBatchLine' }
        destination_country: { type: string, minLength: 2, maxLength: 2 }
        confidence_threshold: { type: number, minimum: 0, maximum: 1 }

    ClassificationResult:
      type: object
      properties:
        hs_6_code: { type: string, example: "851762" }
        recommended_10_digit: { type: string, nullable: true, example: "8517.62.00.90" }
        confidence: { type: number, minimum: 0, maximum: 1 }
        reason: { type: string }
        needs_review: { type: boolean }
        candidates:
          type: array
          items:
            type: object
            properties:
              hts_code: { type: string }
              description: { type: string }
              score: { type: number }
        disagreement_with_printed:
          type: boolean
          description: True when a supplied hs_code_printed differs from the recommendation.

    BatchClassificationResult:
      type: object
      properties:
        results:
          type: array
          items: { $ref: '#/components/schemas/ClassificationResult' }
        needs_review:
          type: array
          items: { type: integer }
        consistency:
          type: object
          properties:
            common_chapter: { type: string }
            outlier_indices:
              type: array
              items: { type: integer }

    CompareRequest:
      type: object
      required: [hs_code, destination, value, origins]
      properties:
        hs_code:
          type: string
          minLength: 6
          example: "8518.30.20"
        destination:
          type: string
          minLength: 2
          maxLength: 2
          example: US
        value: { type: number, exclusiveMinimum: 0 }
        currency: { type: string, minLength: 3, maxLength: 3, default: USD }
        freight: { type: number, minimum: 0, default: 0 }
        insurance: { type: number, minimum: 0, default: 0 }
        incoterm:
          $ref: '#/components/schemas/Incoterm'
          default: FOB
        transport_mode:
          $ref: '#/components/schemas/TransportMode'
          default: ocean
        origins:
          type: array
          minItems: 1
          maxItems: 6
          items:
            type: string
            minLength: 2
            maxLength: 2
        eu_member_state: { type: string, minLength: 2, maxLength: 2 }
        br_state: { type: string, minLength: 2, maxLength: 2 }
        ca_province: { type: string, minLength: 2, maxLength: 2 }
        calculation_date: { type: string, format: date }

    CompareOriginResult:
      type: object
      properties:
        origin: { type: string }
        coverage: { type: boolean }
        coverage_message: { type: string, nullable: true }
        error_code: { type: string, nullable: true }
        total_landed_cost: { type: number }
        duty_amount: { type: number }
        tax_amount: { type: number }
        fees_amount: { type: number }
        customs_value: { type: number }
        currency: { type: string }
        delta_vs_cheapest: { type: number }
        delta_pct: { type: number }
        breakdown_summary:
          type: object
          additionalProperties: { type: number }
        flags:
          type: object
          properties:
            denied_party: { type: boolean }
            pga: { type: boolean }
            restriction: { type: boolean }
            ad_cvd: { type: boolean }
            fta_opportunity: { type: boolean }

    CompareResponse:
      type: object
      properties:
        hs_code: { type: string }
        destination: { type: string }
        currency: { type: string }
        value: { type: number }
        incoterm: { type: string }
        transport_mode: { type: string }
        freight: { type: number }
        insurance: { type: number }
        origins_requested:
          type: array
          items: { type: string }
        results:
          type: array
          items: { $ref: '#/components/schemas/CompareOriginResult' }
        calculated_at: { type: string, format: date-time }

    RatesResponse:
      type: object
      properties:
        country: { type: string }
        hs_code: { type: string }
        origin: { type: string, nullable: true }
        calculation_date: { type: string, format: date }
        mfn_rate: { type: number, nullable: true }
        preferential_rate: { type: number, nullable: true }
        overlays:
          type: object
          properties:
            section_301: { type: number, nullable: true }
            section_301_fl: { type: number, nullable: true }
            section_232: { type: number, nullable: true }
            section_232_pharma: { type: number, nullable: true }
            section_122: { type: number, nullable: true }
            section_338_ca: { type: number, nullable: true }
            ad_cvd: { type: number, nullable: true }
        vat_rate: { type: number, nullable: true }
        sources:
          type: array
          items: { type: string }
        data_as_of:
          type: string
          format: date-time
