Last updated: 5 August 2026
Rails applications dealing with pricing, payments, or international users need accurate exchange rate data. Calling a live currency API every time a user hits a page is slow and wasteful. This guide covers the right way to do it: a clean service object, Rails caching, background rate updates, and proper error handling throughout.
We use the CurrencyFreaks API throughout. The free plan gives you 1,000 calls per month with SSL included — enough for most development and low-traffic production setups. Sign up at currencyfreaks.com/signup to get your API key before starting.
Prerequisites
- Ruby 3.1+ and Rails 7+
- Bundler
- A CurrencyFreaks API key (free at currencyfreaks.com/signup)
Step 1 — Add HTTParty and Configure Credentials
HTTParty is the simplest HTTP library for Rails service objects. Add it to your Gemfile:
gem "httparty"
Run bundle install. Then store your API key in Rails encrypted credentials — never in the codebase:
rails credentials:edit
Add:
currencyfreaks:
api_key: your_api_key_here
Reference it in code as: Rails.application.credentials.currencyfreaks[:api_key]
Step 2 — Create the CurrencyFreaks Service Object
Create app/services/currency_freaks_service.rb:
class CurrencyFreaksService
BASE_URL = "https://api.currencyfreaks.com/v2.0".freeze
def initialize
@api_key = Rails.application.credentials.currencyfreaks[:api_key]
end
def latest_rates(base: "USD", symbols: [])
Rails.cache.fetch(cache_key("latest", base, symbols), expires_in: 1.hour) do
fetch_rates("/rates/latest", base: base, symbols: symbols)
end
end
def historical_rates(date:, base: "USD", symbols: [])
Rails.cache.fetch(cache_key("historical", base, symbols, date), expires_in: 24.hours) do
fetch_rates("/rates/historical", base: base, symbols: symbols, date: date)
end
end
private
def fetch_rates(path, params = {})
response = HTTParty.get(
"#{BASE_URL}#{path}",
query: { apikey: @api_key, base: params[:base], symbols: params[:symbols]&.join(","), date: params[:date] }.compact,
timeout: 8
)
raise "CurrencyFreaks error: #{response.code}" unless response.success?
response.parsed_response
rescue HTTParty::Error, SocketError => e
Rails.logger.error "CurrencyFreaks API unavailable: #{e.message}"
nil
end
def cache_key(*parts)
["currencyfreaks", *parts.map(&:to_s).map { |s| s.gsub(/[^a-z0-9_]/i, "_") }].join("/")
end
end
Step 3 — Use in a Controller
In app/controllers/rates_controller.rb:
class RatesController < ApplicationController
def index
service = CurrencyFreaksService.new
result = service.latest_rates(
base: "USD",
symbols: %w[EUR GBP JPY CAD AUD SGD]
)
if result
@rates = result["rates"]
@updated_at = result["date"]
else
@error = "Exchange rates temporarily unavailable."
end
end
end
Add to routes.rb:
resources :rates, only: [:index]
Step 4 — Render Rates in a View
Create app/views/rates/index.html.erb:
<h1>Live Exchange Rates</h1>
<% if @error %>
<p class="error"><%= @error %></p>
<% else %>
<p>Base: <strong>USD</strong> | Updated: <%= @updated_at %></p>
<table>
<tr><th>Currency</th><th>Rate</th></tr>
<% @rates.each do |currency, rate| %>
<tr><td><%= currency %></td><td><%= "%.4f" % rate.to_f %></td></tr>
<% end %>
</table>
<% end %>
Step 5 — Background Job for Scheduled Rate Updates
For applications that need rates available before any user request arrives, fetch and cache rates on a schedule. Generate a job:
rails generate job FetchExchangeRates
In app/jobs/fetch_exchange_rates_job.rb:
class FetchExchangeRatesJob < ApplicationJob
queue_as :default
CURRENCIES = %w[EUR GBP JPY CAD AUD SGD INR PKR].freeze
def perform
service = CurrencyFreaksService.new
result = service.latest_rates(base: "USD", symbols: CURRENCIES)
if result
Rails.logger.info "Exchange rates refreshed: #{result["date"]}"
else
Rails.logger.error "Exchange rate refresh failed"
end
end
end
Schedule it in config/schedule.rb (using whenever gem) or via Sidekiq-Cron:
# config/schedule.rb (whenever)
every 1.hour do
runner "FetchExchangeRatesJob.perform_later"
end
Error Handling Reference
| HTTP Status | Meaning and Action |
|---|---|
| 401 | Invalid API key — check Rails.application.credentials |
| 429 | Monthly quota exceeded — reduce call frequency or upgrade plan |
| 422 | Invalid parameter — check base and symbols values |
| 5xx | API server error — serve stale cache, retry after 60 seconds |
FAQs
Does this work with Rails 7 and 8?
Yes. The HTTParty, Rails.cache, and ActiveJob patterns used here are stable across Rails 6.1 through 8.
Can I change the base currency from USD?
Yes on paid plans. The free plan uses USD as the only available base currency. Pass base: "EUR" or any ISO 4217 code on a Starter plan or above.
How do I avoid hitting the free plan limit?
The 1.hour cache in latest_rates means a maximum of 24 API calls per day per currency combination. That is 720 calls per month — well within the 1,000 free plan limit for most development setups.
