Tariffs API for Solidus.
A Rails-native way to add US duty calculation to a Solidus store. One HTTP call, drop the result into the order as a calculator or adjustment.
Solidus is built on Rails and so are we, which means the integration is short and predictable. This page shows how to wire Tariffs API into Solidus as a calculator on a shipping rate, as an order adjustment, or as a line-item modifier — whichever fits your store's accounting needs.
Built for Solidus developers.
Rails to Rails
No SDK, no GraphQL client. Net::HTTP or Faraday calls the resolve endpoint and you parse JSON. The whole integration fits in one initializer plus one PORO.
Calculator-compatible
Solidus inherits the Spree::Calculator interface. Tariffs API maps cleanly into a calculator subclass that returns the duty as a money value.
Public, flat pricing
$199/month, 100,000 calls included. Same price for a side project and a Series B Solidus store. No per-shipment metering.
Audit-ready response
Every response includes the Chapter 99 codes applied, the rate components, and a confidence score. Easy to log alongside the order for compliance review.
Drop it in.
Two snippets: a thin client PORO, then a calculator subclass you register with Solidus.
1. Client PORO (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. Solidus calculator (app/models/spree/calculator/tariffs_api_duty.rb)
rubymodule Spree
class Calculator::TariffsApiDuty < Calculator
def self.description
"US duty via Tariffs API"
end
def compute_line_item(line_item)
hts = line_item.variant.product.hts_code
origin = line_item.variant.product.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 the calculator (config/initializers/spree.rb)
rubyRails.application.config.to_prepare do
Rails.application.config.spree.calculators.shipping_methods << Spree::Calculator::TariffsApiDuty
end
One endpoint. Deterministic JSON.
GET /api/v1/tariffs/resolve returns the base duty, every applicable Chapter 99 measure, and a confidence score. The response is stable JSON, easy to memoize per (hts, origin) pair in Rails.cache to keep your call count down.
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 }
}
]
}
Solidus + Tariffs API
Is there an official tariffs_api gem?
Not yet. A Solidus-friendly gem is on the roadmap. For now the integration is the 30 lines of Ruby above, which most teams want to own anyway.
Where should the HTS code live on a Solidus product?
Add an hts_code string column to Spree::Product via a migration, or store it in product properties. Country of origin can live on the variant if it varies by SKU.
How do I avoid hitting the API on every cart update?
Memoize the resolve call in Rails.cache keyed by [hts, origin]. Duty rates change rarely (when a Chapter 99 measure is published) and our response is safe to cache for an hour or more. The /api/v1/tariffs/resolve_batch endpoint is also there for cart-level batching.
Does this work with Spree?
Yes. Solidus is Spree-API compatible, so the same calculator pattern works on Spree. See the dedicated Spree page for Spree-specific notes.
- Solidus Calculator documentation · accessed 2026-05-14
- Solidus order adjustments · accessed 2026-05-14