DEV Community

chathuranga basnayaka
chathuranga basnayaka

Posted on Originally published at allratestoday.com

Exchange Rate API Cheat Sheet: curl and jq Commands

This is an exchange rate API quick reference for the terminal. Every command below calls a real AllRatesToday endpoint with curl, and most pipe into jq to pull out exactly the number you want. Copy the setup block once, then use the sections as a cheat sheet.

Setup: keep the key out of shell history

Never paste the key into a command. It ends up in ~/.bash_history and in screenshots. Read it once into an environment variable instead:

read -rs ALLRATES_API_KEY && export ALLRATES_API_KEY   # type or paste, nothing is echoed
export BASE=https://allratestoday.com
export AUTH="Authorization: Bearer $ALLRATES_API_KEY"
Enter fullscreen mode Exit fullscreen mode

Or keep it in a file only you can read:

mkdir -p ~/.config/allratestoday && chmod 700 ~/.config/allratestoday
read -rs k && printf '%s' "$k" > ~/.config/allratestoday/key && unset k
chmod 600 ~/.config/allratestoday/key
export ALLRATES_API_KEY="$(cat ~/.config/allratestoday/key)"
Enter fullscreen mode Exit fullscreen mode

History now records $AUTH, never the key. Every request authenticates with the Authorization: Bearer header.

Latest rate for one pair

curl -s "$BASE/api/v1/rates?source=USD&target=EUR" -H "$AUTH"
# [{"rate":0.9215,"source":"USD","target":"EUR","time":"2026-04-03T12:00:00Z"}]

curl -s "$BASE/api/v1/rates?source=USD&target=EUR" -H "$AUTH" | jq '.[0].rate'
# 0.9215
Enter fullscreen mode Exit fullscreen mode

The response is an array even for one pair. jq '.rate' returns null; use .[0].rate.

For a flat object with just the rate, use /api/rate:

curl -s "$BASE/api/rate?source=GBP&target=USD" -H "$AUTH"
# {"rate":1.2634,"source":"mid-market"}

curl -s "$BASE/api/rate?source=GBP&target=USD" -H "$AUTH" | jq -r .rate
Enter fullscreen mode Exit fullscreen mode

Rates are mid-market, refreshed every 60 seconds. The time field says which snapshot you got.

Many targets in one request

A comma-separated target returns one array and costs one request:

curl -s "$BASE/api/v1/rates?source=USD&target=EUR,GBP,JPY,CAD" -H "$AUTH" \
  | jq 'map({(.target): .rate}) | add'
# {"EUR":0.9215,"GBP":0.7891,"JPY":151.37,"CAD":1.3842}
Enter fullscreen mode Exit fullscreen mode

As tab-separated lines, ready for sort or column:

curl -s "$BASE/api/v1/rates?source=USD&target=EUR,GBP,JPY,CAD" -H "$AUTH" \
  | jq -r '.[] | "\(.target)\t\(.rate)"' | sort -k2 -n
Enter fullscreen mode Exit fullscreen mode

Let curl build the query string when values come from variables:

FROM=USD; TO=EUR,GBP
curl -sG "$BASE/api/v1/rates" -H "$AUTH" \
  --data-urlencode "source=$FROM" --data-urlencode "target=$TO"
Enter fullscreen mode Exit fullscreen mode

Convert an amount

Add amount to the same endpoint with exactly one target. Conversion is on the Medium plan and above.

curl -s "$BASE/api/v1/rates?source=USD&target=EUR&amount=1000" -H "$AUTH"
# {"from":{"currency":"USD","amount":1000},"to":{"currency":"EUR","amount":923.4},"rate":0.9234,"source":"mid-market"}

curl -s "$BASE/api/v1/rates?source=USD&target=EUR&amount=1000" -H "$AUTH" | jq '.to.amount'
# 923.4
Enter fullscreen mode Exit fullscreen mode

to.amount is rounded to 6 decimal places; rate keeps full precision. There is no /api/convert endpoint.

Historical mid-market rates

History takes a period preset: 1d, 7d, 30d or 1y. The default is 7d, an unknown value silently becomes 7d, and from and to are ignored. History is on paid plans.

curl -s "$BASE/api/historical-rates?source=USD&target=EUR&period=30d" -H "$AUTH" \
  | jq '.data | length'
Enter fullscreen mode Exit fullscreen mode

Lowest and highest point in the period:

curl -s "$BASE/api/historical-rates?source=USD&target=EUR&period=30d" -H "$AUTH" \
  | jq '.data | (min_by(.rate) | {low: .rate, date}), (max_by(.rate) | {high: .rate, date})'
Enter fullscreen mode Exit fullscreen mode

Average, and the change from first to last point:

curl -s "$BASE/api/historical-rates?source=USD&target=EUR&period=30d" -H "$AUTH" \
  | jq '[.data[].rate] | {avg: (add / length), change: (.[-1] - .[0])}'
Enter fullscreen mode Exit fullscreen mode

Save as CSV:

curl -s "$BASE/api/historical-rates?source=USD&target=EUR&period=1y" -H "$AUTH" \
  | jq -r '"date,rate", (.data[] | [.date, .rate] | @csv)' > usd-eur-1y.csv
Enter fullscreen mode Exit fullscreen mode

Billing notes: 1y returns weekly points and costs 12 requests. 1d is an interpolated chart shape, not real intraday data.

Supported currencies

curl -s "$BASE/api/v1/symbols" -H "$AUTH" | jq '.currencies | length'

curl -s "$BASE/api/v1/symbols" -H "$AUTH" \
  | jq -r '.currencies[] | "\(.code)\t\(.symbol)\t\(.name)"'

# exit code 0 if the code exists, 1 if not
curl -s "$BASE/api/v1/symbols" -H "$AUTH" | jq -e '.currencies | any(.code == "XOF")'
Enter fullscreen mode Exit fullscreen mode

Official central bank rates

Latest official table for a bank, with its own rate_date:

curl -s "$BASE/api/v1/central-bank/ecb/latest" -H "$AUTH" | jq '{rate_date, n: (.rates | length)}'

# one quote from the table
curl -s "$BASE/api/v1/central-bank/ecb/latest" -H "$AUTH" \
  | jq '.rates[] | select(.quote == "USD") | .value'
Enter fullscreen mode Exit fullscreen mode

One pair, computed from the bank's table:

curl -s "$BASE/api/v1/central-bank/ecb/latest?source=USD&target=EUR" -H "$AUTH" \
  | jq '{rate, rate_date}'
Enter fullscreen mode Exit fullscreen mode

The table for a past date. A weekend or holiday returns the most recent table, and rate_date shows which:

curl -s "$BASE/api/v1/central-bank/ecb/2026-11-10" -H "$AUTH" | jq .rate_date
Enter fullscreen mode Exit fullscreen mode

A date range for one pair. This is on paid plans and billed per month of the range, so the example below is three months:

curl -s "$BASE/api/v1/central-bank/ecb/history?source=USD&target=EUR&from=2026-01-01&to=2026-03-31" \
  -H "$AUTH" -o ecb-usd-eur-q1.json
Enter fullscreen mode Exit fullscreen mode

Replace ecb with any of the 123 central bank and 6 tax authority codes listed on the central bank rates API page.

CSV, Excel and XML

Table endpoints accept ?format=json|csv|xml|xlsx. Write files with -o:

curl -s "$BASE/api/v1/central-bank/ecb/latest?format=csv"  -H "$AUTH" -o ecb.csv
curl -s "$BASE/api/v1/central-bank/ecb/latest?format=xlsx" -H "$AUTH" -o ecb.xlsx
curl -s "$BASE/api/v1/central-bank/ecb/latest?format=xml"  -H "$AUTH" -o ecb.xml
Enter fullscreen mode Exit fullscreen mode

The CSV starts with a UTF-8 byte order mark so Excel reads it correctly. Strip it for command-line tools:

sed '1s/^\xEF\xBB\xBF//' ecb.csv | column -s, -t | head
Enter fullscreen mode Exit fullscreen mode

Status codes and quota headers

Print only the status code:

curl -s -o /dev/null -w '%{http_code}\n' "$BASE/api/rate?source=USD&target=EUR" -H "$AUTH"
Enter fullscreen mode Exit fullscreen mode

See headers and body together:

curl -si "$BASE/api/rate?source=USD&target=EUR" -H "$AUTH"
Enter fullscreen mode Exit fullscreen mode

On a 429, the quota headers tell you where you stand:

curl -s -D - -o /dev/null "$BASE/api/rate?source=USD&target=EUR" -H "$AUTH" | grep -i '^x-monthly'
# x-monthly-limit: ...
# x-monthly-used: ...
# x-monthly-resets: ...
Enter fullscreen mode Exit fullscreen mode

These headers appear only on 429 responses. A 200 carries no rate-limit headers, so an empty grep on a successful call is normal.

A 401 means a missing or wrong key:

curl -s "$BASE/api/rate?source=USD&target=EUR"   # no Authorization header
# {"error":"A valid API key is required","hint":"Get a free API key at https://allratestoday.com/register"}
Enter fullscreen mode Exit fullscreen mode

Rejected 401, 403 and 429 requests are not charged. In scripts, use --fail-with-body so curl exits non-zero on an error status and still prints the JSON error.

Endpoint summary

Task Endpoint
One pair, array /api/v1/rates?source=USD&target=EUR
One pair, flat /api/rate?source=GBP&target=USD
Many targets, one request /api/v1/rates?source=USD&target=EUR,GBP,JPY
Convert (Medium+) /api/v1/rates?source=USD&target=EUR&amount=1000
History (paid) /api/historical-rates?source=USD&target=EUR&period=30d
Currencies /api/v1/symbols
Official latest /api/v1/central-bank/ecb/latest
Official by date /api/v1/central-bank/ecb/2026-11-10
Official range (paid) /api/v1/central-bank/ecb/history?source=USD&target=EUR&from=...&to=...

There are no /api/latest, /api/convert or /api/timeseries endpoints; those paths return 404. The API docs have the full reference, and the free currency API comparison covers other providers if you are still choosing.

FAQ

Why does jq '.rate' return null on /api/v1/rates?

Because the response is an array, even for one pair. Use '.[0].rate', or map the array to an object with 'map({(.target): .rate}) | add'.

Can I pass from and to dates to /api/historical-rates?

No. That endpoint takes a period of 1d, 7d, 30d or 1y and ignores from and to. An unknown period silently falls back to 7d. For an exact date range of official rates, use /api/v1/central-bank/ecb/history with from and to.

Why do I not see X-RateLimit headers?

Successful responses carry no rate-limit headers. The X-Monthly-Limit, X-Monthly-Used and X-Monthly-Resets headers appear only on a 429. Check them with curl -i or curl -D -.

How do I keep my API key out of shell history?

Read it with read -rs, which does not echo or record what you type, or load it from a file with chmod 600. Commands then refer to $ALLRATES_API_KEY, so history only ever stores the variable name.

How do I open the CSV in Excel without odd characters?

The CSV starts with a UTF-8 byte order mark so Excel detects the encoding. Excel handles it. For command-line tools, strip it with sed '1s/^\xEF\xBB\xBF//'.

Originally published at allratestoday.com.

Top comments (0)