Multi-currency exchange rate database manager that fetches historical exchange rates from OpenExchangeRates.org and stores them in SQLite. Supports 168 currencies with simple query, update, and real-time operations. Single ~300-line BCS-compliant Bash script.
# 1. Install
curl -sSL https://raw.githubusercontent.com/Open-Technology-Foundation/openxchg/main/install.sh | bash
# 2. Set your API key (get free key at openexchangerates.org)
export OPENEXCHANGE_API_KEY='your_api_key_here'
# 3. Update database (fetches all currency rates for yesterday)
openxchg idr
# 4. Query rates
openxchg idr usd eur gbpopenxchg is a command-line tool that:
- Fetches exchange rates for 168 currencies from the OpenExchangeRates.org API
- Stores historical rate data in a SQLite database (table-per-base-currency architecture)
- Provides three modes: UPDATE (populate database), QUERY (retrieve stored rates), LATEST (display current rates without storing)
- Supports any currency as base (IDR, USD, EUR, GBP, etc.) with automatic cross-rate conversion
- Bash 5.2+
- sqlite3 3.45+
- wget
- jq
- OpenExchangeRates.org API key (free tier: 1,000 requests/month)
curl -sSL https://raw.githubusercontent.com/Open-Technology-Foundation/openxchg/main/install.sh | bashChecks dependencies, installs openxchg to /usr/local/bin, and creates /var/lib/openxchg/.
git clone https://github.com/Open-Technology-Foundation/openxchg.git
cd openxchg
sudo apt-get install -y sqlite3 wget jq
sudo make install| Target | Action |
|---|---|
make install |
Install to $(PREFIX)/bin (default /usr/local), create /var/lib/openxchg/ |
make uninstall |
Remove the binary (keeps the database directory) |
make check |
Verify openxchg resolves in PATH |
make test |
Run the offline BATS suite |
Supports PREFIX and DESTDIR for custom prefixes and staged/package builds (make DESTDIR=/tmp/pkg install).
- Sign up at openexchangerates.org
- Free tier provides 1,000 requests/month with historical data access
- Copy your App ID from the dashboard and export it (see below)
Environment variables cover the common cases; an optional config file adds persistence (useful for cron, which has no user environment).
| Variable | Default | Purpose |
|---|---|---|
OPENEXCHANGE_API_KEY |
— | API key (or use -a KEY per invocation) |
DB_PATH |
/var/lib/openxchg/xchg.db |
SQLite database file |
DEFAULT_BASE |
IDR |
Base currency when none given (config file only) |
VERBOSE |
1 |
Default verbosity (config file only) |
# Permanent API key (add to ~/.bashrc)
echo "export OPENEXCHANGE_API_KEY='your_api_key_here'" >> ~/.bashrc
# Per-user database
DB_PATH=~/xchg.db openxchg idr usd eurPlain Bash assignments, sourced from (later overrides earlier):
/etc/openxchg.conf— system-wide${XDG_CONFIG_HOME:-~/.config}/openxchg.conf— per-user
# /etc/openxchg.conf
DEFAULT_BASE=IDR
OPENEXCHANGE_API_KEY='your_api_key_here'Precedence: CLI options > environment variables > user conf > system conf > built-in defaults.
▲ Config files are sourced as Bash — keep them owned by root (system) or the user (personal) and never world-writable. chmod 600 any file containing an API key.
openxchg [OPTIONS] [base_currency] [target_currencies...]No target currencies given — fetch all rates for the base currency and date:
openxchg idr # IDR table, yesterday's rates (default date)
openxchg -d 2025-01-01 eur # EUR table, specific date
openxchg -q usd # quiet (for cron)Important: UPDATE a base currency table before you can QUERY it.
openxchg idr usd eur gbp # IDR table: latest stored USD, EUR, GBP
openxchg aud usd sgd # AUD table
openxchg -d 2025-01-01 eur usd gbp # specific date (most recent at or before)
openxchg eur -d 2025-01-15 usd gbp # options can appear anywhere (GNU-style)
# In scripts
read -r currency value date < <(openxchg -q idr eur)Fetch current rates from the API without storing them:
openxchg --latest idr usd eur gbp
openxchg -lq aud usd sgd # quiet, bundled options
openxchg -l eur # all currencies for EUR baseCurrency Xchg Date (UTC)
---------- -------------- ----------
USD 16712 2025-11-14
EUR 19426.865616 2025-11-14
GBP 21989.647286 2025-11-14
| Option | Description |
|---|---|
-d, --date DATE |
Date for query/update (default: yesterday UTC) |
-a, --apikey KEY |
API key (overrides OPENEXCHANGE_API_KEY) |
-l, --latest |
Fetch real-time rates (display only, not stored) |
-q, --quiet |
Suppress informational output |
-v, --verbose |
Verbose output (default) |
-V, --version |
Display version |
-h, --help |
Display help message |
Short options bundle (-lq, -qd DATE) and may appear anywhere on the command line.
SQLite database with a table-per-base-currency architecture. Each base currency (IDR, USD, EUR, ...) maintains its own table, created automatically on first update.
CREATE TABLE {CURRENCY} (
id INTEGER PRIMARY KEY,
Date DATE NOT NULL,
Currency TEXT NOT NULL DEFAULT 'USD',
Unit INTEGER NOT NULL DEFAULT 1,
Xchg REAL NOT NULL DEFAULT 0.0,
Updated TIMESTAMP NOT NULL,
UNIQUE(Date, Currency)
);
CREATE INDEX idx_{CURRENCY}_currency ON {CURRENCY}(Currency);
CREATE INDEX idx_{CURRENCY}_updated ON {CURRENCY}(Updated);UNIQUE(Date, Currency)prevents duplicates; re-running an update for the same date replaces rows- All dates and timestamps are UTC (matching API convention)
- Every currency operand is validated (
^[A-Z]{3}$+ supported-list membership) before reaching SQL
The API provides all rates relative to USD. Cross rates are computed as:
exchange_rate = base_currency_rate / target_currency_rate
Example: IDR base, EUR target — API gives USD/IDR = 16712, USD/EUR = 0.86; stored rate = 16712 / 0.86 = 19432.55...
Use sqlite3 directly:
sqlite3 /var/lib/openxchg/xchg.db .schema
sqlite3 /var/lib/openxchg/xchg.db "SELECT name FROM sqlite_master WHERE type='table'"
sqlite3 /var/lib/openxchg/xchg.db "VACUUM"
sqlite3 /var/lib/openxchg/xchg.db "PRAGMA integrity_check"
sqlite3 /var/lib/openxchg/xchg.db -header -column \
"SELECT * FROM IDR WHERE Currency='USD' ORDER BY Date DESC LIMIT 10"Common names resolve to ISO codes:
| Alias | Maps To |
|---|---|
| DOLLAR, GREENBACK | USD |
| STERLING, POUND | GBP |
| YEN | JPY |
| YUAN, RENMINBI, RMB | CNY |
| RUPIAH | IDR |
| FRANC, SWISSY | CHF |
| AUSSIE | AUD |
| KIWI | NZD |
| LOONIE | CAD |
openxchg idr dollar yen yuan # same as: openxchg idr usd jpy cnyQuery returns nothing / "No table" — UPDATE the base table first:
openxchg idr && openxchg idr usd eurAPI key errors:
echo $OPENEXCHANGE_API_KEY # verify key is set
openxchg -a YOUR_API_KEY -l idr usd # test with explicit keyDatabase permission issues — /var/lib/openxchg/ must be writable by the updating user, or point DB_PATH at a location you own:
DB_PATH=~/xchg.db openxchg idr168 currencies:
AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF BMD BND BOB BRL BSD BTC BTN BWP BYN BZD CAD CDF CHF CLF CLP CNH CNY COP CRC CUC CUP CVE CZK DJF DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GGP GHS GIP GMD GNF GTQ GYD HKD HNL HRK HTG HUF IDR ILS IMP INR IQD IRR ISK JEP JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK LBP LKR LRD LSL LYD MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SDG SEK SGD SHP SLL SOS SRD SSP STD STN SVC SYP SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VND VUV WST XAF XAG XAU XCD XDR XOF XPD XPF XPT YER ZAR ZMW ZWL
BATS test suite, 33 tests, fully offline (mock wget + scratch databases + isolated config):
make test # or: ./scripts/run_tests.shSee tests/README.md for details.
- Default date: yesterday UTC (API provides End-of-Day rates for completed UTC days)
- Case insensitive: currency codes and aliases are normalized to uppercase
- Precision: rates displayed to 6 decimal places, trailing zeros trimmed
- Rate limits: free tier API key provides 1,000 requests/month
- Historical data: available from 1999-01-01 onwards (API dependent)
Contributions welcome — submit a Pull Request.
git clone https://github.com/Open-Technology-Foundation/openxchg.git
cd openxchg
sudo apt-get install sqlite3 wget jq
./openxchg --help
./scripts/run_tests.shFollows the Bash Coding Standard (BCS):
- 2-space indentation (strictly enforced)
set -euo pipefailandshopt -s inherit_errexitat start- Typed declarations (
declare/localwith-i/-a/-A/--) - Prefer
[[over[for conditionals var+=1for increments, never((var++))- Scripts end with
#finmarker - Verify with
shellcheck -xandbcscheck
GNU General Public License v3.0 - see LICENSE for details.
Gary Dean (Biksu Okusi)
- Website: garydean.id
- Founder/Chairman: Okusi Group
- Location: Bali, Indonesia
- Exchange rate data provided by OpenExchangeRates.org
- Testing framework: BATS
- Follows Bash Coding Standard
- Documentation: GitHub Repository
- Issues: Report bugs
- API Documentation: OpenExchangeRates API
Version: 2.0.0 | License: GPL v3.0 | Status: Production Ready