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> &nbsp;|&nbsp; 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.

Sign up for your free CurrencyFreaks API key and start integrating exchange rates into your Rails app today.