Spree Commerce integration

Tariffs API for Spree.

Add US duty and tariff calculation to Spree Commerce with a Spree::Calculator subclass and a single HTTP call. No new platform, no contract.

Spree's calculator and adjustment system was built exactly for this kind of plug-in. This page shows how to compute US duty on a per-line-item basis using Tariffs API, register it as a Spree calculator, and surface it on the order. If you're running Solidus, the same code works there too.

Why this works

Built for Spree developers.

Native Spree pattern

Subclass Spree::Calculator, return a money amount, register in an initializer. The integration is idiomatic Rails.

Per-line-item or order-level

Compute duty per SKU for clean audit trails, or aggregate at order level for fewer API calls. Both shapes fit Spree's adjustment model.

No SaaS lock-in

You own the calculator code. We're the data source. Drop us and swap to another provider without touching your checkout flow.

US stacking out of the box

Section 301, 232, IEEPA, and Chapter 99 measures all returned in one call. No second integration to capture the additional duties.

In your code

Drop it in.

The integration is three files: a service client, a Spree calculator, and an initializer to register it.

1. Client (app/services/tariffs_api.rb)

ruby
require "net/http"
require "json"

class TariffsApi
  BASE = "https://tariffsapi.com/api/v1"

  def self.resolve(hts:, origin:)
    uri = URI("#{BASE}/tariffs/resolve?hts=#{hts}&origin=#{origin}")
    req = Net::HTTP::Get.new(uri)
    req["Authorization"] = "Bearer #{ENV.fetch('TARIFFSAPI_KEY')}"
    res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
    JSON.parse(res.body)
  end
end

2. Spree calculator

ruby
module Spree
  class Calculator::TariffsApiDuty < Calculator
    def self.description
      "US duty via Tariffs API"
    end

    def compute_line_item(line_item)
      product = line_item.variant.product
      hts = product.property("hts_code")
      origin = product.property("country_of_origin")
      return 0 if hts.blank? || origin.blank?

      data = TariffsApi.resolve(hts: hts, origin: origin)
      rate = data.dig("summary", "total_resolved_ad_valorem_rate").to_f / 100
      (line_item.amount * rate).round(2)
    end
  end
end

3. Register (config/initializers/spree.rb)

ruby
Rails.application.config.spree.calculators.shipping_methods << Spree::Calculator::TariffsApiDuty
What you get back

One endpoint. Deterministic JSON.

The resolve endpoint returns a deterministic JSON shape with summary rates, base HTSUS duty, every applicable Chapter 99 additional measure, and a confidence score. Cache by [hts, origin] in Rails.cache to keep request volume low.

GET /api/v1/tariffs/resolve?hts=8541.10.00.80&origin=CN

{
  "summary": {
    "applicable_ad_valorem_rate": 0.0,
    "resolved_additional_ad_valorem_rate": 25.0,
    "total_resolved_ad_valorem_rate": 25.0
  },
  "base_tariff": { "percentage_component": 0.0 },
  "additional_measures": [
    {
      "program": "section_301",
      "chapter_99_code": "9903.91.05",
      "resolved_rate": { "percentage_component": 0.25 }
    }
  ]
}
FAQ

Spree + Tariffs API

Is there an official Spree gem?

Not yet. The Solidus + Spree gem is on the Phase 2 roadmap. For now the integration is the small amount of Ruby above, which is easy to vendor and own.

Where do I store the HTS code on a Spree product?

Spree::Property is the cleanest fit. Create properties for hts_code and country_of_origin, set them per product, and read them with product.property(...) in your calculator.

What about variant-level country of origin?

If country of origin varies by SKU, store it on Spree::Variant via a new column or a variant-level property. The calculator can prefer variant data when present.

How does this compare to a full cross-border platform like Zonos?

Different scope. Zonos handles classification, shipping, and fraud. Tariffs API is just the duty number. Most Spree stores already have classification done and shipping wired up via Spree's shipping calculators, so the duty lookup is the missing piece.

Sources

Add US duty to your Spree checkout in an afternoon.