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.
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.
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)
rubyrequire "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
rubymodule 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)
rubyRails.application.config.spree.calculators.shipping_methods << Spree::Calculator::TariffsApiDuty
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 }
}
]
}
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.
- Spree Calculator guide · accessed 2026-05-14
- Spree Property model · accessed 2026-05-14