UniRate Elixir Client
Official Elixir client for the UniRate API — free currency exchange rates, historical data, and VAT rates.
Built on the Erlang/OTP standard library only (:httpc for HTTP and the OTP 27
:json module for parsing): zero third-party dependencies.
Requirements
- Elixir
~> 1.17 - Erlang/OTP 27 or newer (the
:jsonmodule ships with OTP 27)
Installation
Add unirate_api to your deps in mix.exs:
def deps do
[
{:unirate_api, "~> 0.1.0"}
]
end
Then run mix deps.get.
Quick start
client = UniRate.Client.new("your-api-key")
# A single exchange rate.
UniRate.Client.get_rate(client, "USD", "EUR")
#=> 0.92
# All rates for a base currency.
UniRate.Client.get_rate(client, "USD")
#=> %{"EUR" => 0.92, "GBP" => 0.79, ...}
# Convert an amount.
UniRate.Client.convert(client, "EUR", from: "USD", amount: 100)
#=> 92.5
# Supported currencies.
UniRate.Client.get_supported_currencies(client)
#=> ["USD", "EUR", "GBP", ...]
# VAT rate for a country.
UniRate.Client.get_vat_rate(client, "DE")
#=> %{"country_code" => "DE", "country_name" => "Germany", "vat_rate" => 19.0}
Currency and country codes are upper-cased automatically before being sent.
Configuration
UniRate.Client.new("your-api-key",
timeout: 30_000, # request timeout in milliseconds
base_url: "https://api.unirateapi.com"
)
API
Current data (free tier)
| Method | Returns |
|---|---|
get_rate(client, from \\ "USD", to \\ nil, opts \\ []) |
a number when to is set, else a %{code => rate} map |
convert(client, to, opts \\ []) |
the converted amount (options: :amount, :from) |
get_supported_currencies(client, opts \\ []) |
a list of currency-code strings |
get_vat_rates(client, opts \\ []) |
the full VAT response (option: :country) |
get_vat_rate(client, country, opts \\ []) |
the vat_data object for one ISO-3166 alpha-2 country |
Historical data (Pro plan)
These endpoints require a Pro subscription and return 403 on the free tier
(surfaced as a UniRate.Error with reason: :api and status: 403).
| Method |
|---|
get_historical_rate(client, date, opts \\ []) — options :amount, :from, :to |
get_historical_rates(client, date, opts \\ []) — options :amount, :base |
convert_historical(client, amount, from, to, date, opts \\ []) |
get_time_series(client, start_date, end_date, opts \\ []) — options :amount, :base, :currencies |
get_historical_limits(client, opts \\ []) |
Every method also accepts :format ("json" default, or "xml" / "csv" /
"tsv") and :callback. When :format is not "json", the raw response body
string is returned instead of a parsed value.
Error handling
Every failure is a UniRate.Error — rescue the whole family with one clause and
narrow on :reason:
try do
UniRate.Client.get_rate(client, "USD", "ZZZ")
rescue
e in UniRate.Error ->
case e.reason do
:invalid_currency -> IO.puts("unknown currency")
:authentication -> IO.puts("bad API key")
:rate_limit -> IO.puts("slow down")
:api -> IO.puts("API error #{e.status}: #{e.message}")
:transport -> IO.puts("network error: #{e.message}")
_ -> reraise(e, __STACKTRACE__)
end
end
| Reason | HTTP | Meaning |
|---|---|---|
:invalid_date |
400 | Invalid request parameters |
:authentication |
401 | Missing or invalid API key |
:api |
403 | Endpoint requires a Pro subscription |
:invalid_currency |
404 | Currency not found or no data available |
:rate_limit |
429 | Rate limit exceeded |
:api |
503 / other | Service unavailable / generic (carries status + body) |
:transport |
— | Network/transport failure or unparseable response |
Rate limits
The free tier is rate-limited. On HTTP 429 the client raises a UniRate.Error
with reason: :rate_limit; back off and retry.
Testing
mix test # mock tests only (default)
UNIRATE_API_KEY=... mix test --include live # + live free-tier tests
Mock tests inject a transport function via the :http option, so they never hit
the network.
Related clients
UniRate ships official clients for many languages — Python, Node/TypeScript, Go, Rust, Ruby, PHP, Java, Swift, and more. See github.com/UniRate-API.
License
MIT © 2026 Unirate Team. See LICENSE.