Official Racket client for the UniRate API — free currency exchange rates, historical data, and VAT rates.
Built on the Racket distribution only (net/url, net/uri-codec, json) —
zero third-party dependencies.
raco pkg install unirate-apiThen require it:
(require unirate-api)(require unirate-api)
(define client (make-unirate-client "your-api-key"))
;; Current rate (USD -> EUR).
(get-rate client "USD" "EUR") ; => 0.92
;; All rates for a base currency.
(get-rate client "USD") ; => (hash "EUR" 0.92 "GBP" 0.79 ...)
;; Convert an amount.
(convert client "EUR" #:amount 100 #:from "USD") ; => 92.5
;; Supported currencies.
(get-supported-currencies client) ; => '("USD" "EUR" "GBP" ...)
;; VAT rate for a country.
(get-vat-rate client "DE") ; => (hash 'country_code "DE" 'vat_rate 19.0 ...)Get a free API key at https://unirateapi.com.
Construct a client with make-unirate-client:
(make-unirate-client api-key
#:timeout 30 ; seconds
#:base-url "https://api.unirateapi.com")Currency and country codes are upper-cased automatically before being sent.
| Procedure | Returns |
|---|---|
(get-rate client [from "USD"] [to #f]) |
a number when to is given, otherwise a hash of code -> rate |
(convert client to #:amount 1 #:from "USD") |
the converted amount (a number) |
(get-supported-currencies client) |
a list of currency-code strings |
(get-vat-rates client) |
the full VAT response (total_countries, date, vat_rates) |
(get-vat-rate client country) |
the vat_data object for one ISO-3166 alpha-2 country |
These endpoints require a Pro subscription and return a 403 on the free tier
(surfaced as exn:fail:unirate:api? with status 403).
| Procedure | Returns |
|---|---|
(get-historical-rate client date #:amount 1 #:from "USD" #:to #f) |
a number (with to) or a hash (without) |
(get-historical-rates client date #:amount 1 #:base "USD") |
a hash of code -> rate |
(convert-historical client amount from to date) |
the converted amount (a number) |
(get-time-series client start-date end-date #:amount 1 #:base "USD" #:currencies #f) |
a hash of date -> (hash code -> rate) |
(get-historical-limits client) |
the parsed coverage response |
Every procedure 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.
All errors are subtypes of exn:fail:unirate, so you can catch the whole
family or narrow to a specific case:
(require unirate-api)
(with-handlers ([exn:fail:unirate:invalid-currency? (lambda (e) 'bad-currency)]
[exn:fail:unirate:rate-limit? (lambda (e) 'slow-down)]
[exn:fail:unirate:api?
(lambda (e)
(printf "API error ~a: ~a\n"
(exn:fail:unirate:api-status e)
(exn-message e)))]
[exn:fail:unirate? (lambda (e) (exn-message e))])
(get-rate client "USD" "ZZZ"))| HTTP status | Error predicate |
|---|---|
| 400 | exn:fail:unirate:invalid-date? |
| 401 | exn:fail:unirate:authentication? |
| 403 | exn:fail:unirate:api? (status 403 — Pro-gated) |
| 404 | exn:fail:unirate:invalid-currency? |
| 429 | exn:fail:unirate:rate-limit? |
| 503 | exn:fail:unirate:api? (status 503) |
| other / network | exn:fail:unirate:api? / exn:fail:unirate? |
exn:fail:unirate:api also carries exn:fail:unirate:api-status and
exn:fail:unirate:api-body accessors.
Free-tier keys are rate limited. A 429 response raises
exn:fail:unirate:rate-limit?; back off and retry.
raco test tests/client-test.rkt # mock tests, no network
UNIRATE_API_KEY=your-key raco test tests/live-test.rkt # live, free-tier onlyUniRate ships official clients for many languages, including Python, Node/TS, Go, Rust, Ruby, PHP, Java, Swift, and .NET. See the UniRate-API organization.
MIT © 2026 Unirate Team. See LICENSE.