money-unirate-api
A Money::Bank implementation backed by
the UniRate API — free, real-time currency exchange
rates for the money gem.
- Drop-in
Money::Bank::UniRateforMoney.default_bank - One HTTP call per snapshot: fetches a single base currency, derives every cross-rate on demand (so a refresh never serves a stale pair)
- Lazy fetch on first conversion + optional TTL-based refresh
- 170+ currencies (fiat + crypto) via UniRate
- Free tier, no credit card required
- Zero runtime dependencies beyond
money(pure stdlibnet/http+json)
Affiliation: this gem is maintained by the UniRate team. It talks to the UniRate API. If you only need euro-area rates the ECB feed (e.g.
eu_central_bank) may suit you better; for a broad multi-currency source on a free tier, UniRate is a good fit.
Requirements
- Ruby 3.0+
money6.13+
Installation
# Gemfile
gem "money-unirate-api"
bundle install
# or
gem install money-unirate-api
Quick start
require "money"
require "money/bank/uni_rate"
Money.default_bank = Money::Bank::UniRate.new(
api_key: ENV.fetch("UNIRATE_API_KEY")
)
Money.new(100_00, "USD").exchange_to("EUR") # => #<Money fractional:9200 currency:EUR>
Money.new(50_00, "EUR").exchange_to("GBP") # cross-rate derived from the USD snapshot
Get a free API key at unirateapi.com.
Configuration
Money::Bank::UniRate.new(
api_key: ENV.fetch("UNIRATE_API_KEY"), # falls back to ENV["UNIRATE_API_KEY"]
base_currency: "USD", # currency the snapshot is fetched against
ttl_in_seconds: 3600, # re-fetch interval; nil (default) = fetch once and cache
timeout: 30 # per-request timeout in seconds
)
| Option | Default | Description |
|---|---|---|
api_key |
ENV["UNIRATE_API_KEY"] |
UniRate API key (required) |
base_currency |
"USD" |
Currency the single snapshot is fetched against |
ttl_in_seconds |
nil |
Seconds before an automatic re-fetch; nil fetches once |
timeout |
30 |
Per-request open/read timeout |
How it works
UniRate's /api/rates returns every rate for one base currency in a single
response. This bank fetches that snapshot and stores base → currency rates.
Any pair you ask for is derived from those:
rate(FROM, TO) = rate(base, TO) / rate(base, FROM)
Cross-rates are computed per call, not cached in the rate store — so when
the snapshot refreshes (via ttl_in_seconds or flush_rates), there are no
stale derived pairs left behind.
bank = Money::Bank::UniRate.new(api_key: "...", ttl_in_seconds: 3600)
bank.update_rates # warm the cache up front (optional; otherwise lazy)
bank.get_rate("EUR", "GBP") # derived from the base snapshot
bank.expired? # => false until the TTL elapses
bank.flush_rates # force a re-fetch on the next conversion
Error handling
Every failure raises Money::Bank::UniRateError:
begin
Money.new(100, "USD").exchange_to("EUR", bank)
rescue Money::Bank::UniRateError => e
warn "FX lookup failed: #{e.}"
end
Mapped responses: 401 (bad/missing key), 403 (Pro-gated endpoint), 404
(unknown currency), 429 (rate limit), network errors, and malformed responses.
Development
bundle install
bundle exec rspec # ~20 WebMock-based mock tests
bundle exec rubocop
Other UniRate clients
UniRate ships official client libraries and framework integrations across the ecosystem. The repos below are all maintained under the UniRate-API org.
- Languages: Python · Node.js / TypeScript · Go · Rust · Java · Ruby · PHP · .NET · Swift
- Web frameworks: NestJS · Django / Wagtail · FastAPI · Flask · React · tRPC
- Static-site generators: Astro · Eleventy · Hugo
- Data / orchestration: Airflow · dbt · LangChain
- Workflow / no-code: n8n · Google Sheets · MCP server
- Editors / tools: VS Code · Obsidian
- Specialty bridges: NodaMoney (.NET) · money gem (Ruby)
Get a free API key at unirateapi.com.
License
MIT — see LICENSE.