How to Build a Real-Time Currency Converter in Python Using the RBA Exchange Rates API
The Reserve Bank of Australia publishes official exchange rates daily, but accessing this data programmatically means dealing with HTML scraping or complex XML feeds. Here's how to build a reliable currency converter using clean JSON data from the Exchange Rates API.
What We're Building
By the end of this tutorial, you'll have:
- A command-line currency converter
- Functions for real-time and historical conversions
- Proper error handling and rate limiting
- Code you can integrate into larger applications
Prerequisites
You'll need Python 3.7+ and the requests library:
pip install requests
Looking for other languages? We also have tutorials for JavaScript/React and PHP/WordPress implementations.
Getting Your API Key
Sign up at app.exchangeratesapi.com.au to get your free API key. The free tier gives you 300 requests per month, perfect for development and small projects.
Basic Currency Converter
Let's start with a simple converter that gets the latest rates:
import requests
from datetime import datetime
class CurrencyConverter:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.exchangeratesapi.com.au"
self.headers = {
"Authorization": f"Bearer {api_key}"
}
def get_latest_rates(self):
"""Get current exchange rates for all currencies"""
url = f"{self.base_url}/latest"
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
data = response.json()
if data.get('success'):
return data
else:
print(f"API Error: {data.get('error', {}).get('info', 'Unknown error')}")
return None
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
def convert_currency(self, from_currency, to_currency, amount):
"""Convert amount from one currency to another using latest rates"""
url = f"{self.base_url}/convert"
params = {
'from': from_currency,
'to': to_currency,
'amount': amount
}
try:
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
if data.get('success'):
return data['result']
else:
print(f"Conversion failed: {data.get('error', {}).get('info', 'Unknown error')}")
return None
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
# Usage example
converter = CurrencyConverter("your_api_key_here")
# Convert 100 AUD to USD
result = converter.convert_currency("AUD", "USD", 100)
if result:
print(f"100 AUD = {result:.2f} USD")
Adding Historical Data
The RBA API provides historical data back to 2018. Here's how to get rates for specific dates:
def get_historical_rates(self, date):
"""Get exchange rates for a specific date (YYYY-MM-DD format)"""
url = f"{self.base_url}/{date}"
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
data = response.json()
if data.get('success'):
return data
else:
print(f"API Error: {data.get('error', {}).get('info', 'Unknown error')}")
return None
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
def convert_historical(self, from_currency, to_currency, amount, date):
"""Convert currencies using rates from a specific date"""
url = f"{self.base_url}/convert"
params = {
'from': from_currency,
'to': to_currency,
'amount': amount,
'date': date
}
try:
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
if data.get('success'):
return {
'result': data['result'],
'rate': data['info']['rate'],
'date': data['date']
}
else:
print(f"Conversion failed: {data.get('error', {}).get('info', 'Unknown error')}")
return None
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
# Add these methods to your CurrencyConverter class
# Example usage
historical_result = converter.convert_historical("AUD", "USD", 100, "2024-01-01")
if historical_result:
print(f"On {historical_result['date']}: 100 AUD = {historical_result['result']:.2f} USD")
print(f"Exchange rate: {historical_result['rate']:.4f}")
Command Line Interface
Let's create a user-friendly command-line tool:
import argparse
from datetime import datetime, timedelta
def main():
parser = argparse.ArgumentParser(description='Currency Converter using RBA Exchange Rates')
parser.add_argument('--api-key', required=True, help='Your API key')
parser.add_argument('--from', dest='from_currency', required=True, help='Source currency (e.g., AUD)')
parser.add_argument('--to', dest='to_currency', required=True, help='Target currency (e.g., USD)')
parser.add_argument('--amount', type=float, required=True, help='Amount to convert')
parser.add_argument('--date', help='Historical date (YYYY-MM-DD format)')
parser.add_argument('--list-currencies', action='store_true', help='List available currencies')
args = parser.parse_args()
converter = CurrencyConverter(args.api_key)
if args.list_currencies:
rates_data = converter.get_latest_rates()
if rates_data:
print("Available currencies:")
print("AUD (Australian Dollar) - Base currency")
for currency in sorted(rates_data['rates'].keys()):
print(f"{currency}")
return
if args.date:
result = converter.convert_historical(
args.from_currency,
args.to_currency,
args.amount,
args.date
)
if result:
print(f"{args.amount} {args.from_currency} = {result['result']:.2f} {args.to_currency}")
print(f"Rate on {result['date']}: {result['rate']:.6f}")
else:
result = converter.convert_currency(args.from_currency, args.to_currency, args.amount)
if result:
print(f"{args.amount} {args.from_currency} = {result:.2f} {args.to_currency}")
if __name__ == "__main__":
main()
Save this as currency_converter.py and use it like this:
# Current rates
python currency_converter.py --api-key your_key --from AUD --to USD --amount 100
# Historical rates
python currency_converter.py --api-key your_key --from AUD --to EUR --amount 50 --date 2024-01-01
# List available currencies
python currency_converter.py --api-key your_key --list-currencies
Rate Limiting and Caching
For production use, add caching to avoid unnecessary API calls:
import time
from functools import lru_cache
class CachedCurrencyConverter(CurrencyConverter):
def __init__(self, api_key, cache_duration=300): # 5-minute cache
super().__init__(api_key)
self.cache_duration = cache_duration
self._latest_cache = None
self._cache_timestamp = 0
def get_latest_rates(self):
"""Get latest rates with caching"""
current_time = time.time()
# Return cached data if still valid
if (self._latest_cache and
current_time - self._cache_timestamp < self.cache_duration):
return self._latest_cache
# Fetch new data
data = super().get_latest_rates()
if data:
self._latest_cache = data
self._cache_timestamp = current_time
return data
@lru_cache(maxsize=100)
def get_historical_rates_cached(self, date):
"""Cache historical rates (they don't change)"""
return super().get_historical_rates(date)
Error Handling Best Practices
Handle common API errors gracefully:
def safe_convert(self, from_currency, to_currency, amount, date=None, retries=3):
"""Convert with retry logic and detailed error handling"""
for attempt in range(retries):
try:
if date:
result = self.convert_historical(from_currency, to_currency, amount, date)
else:
result = self.convert_currency(from_currency, to_currency, amount)
if result is not None:
return result
except requests.exceptions.Timeout:
if attempt < retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
continue
print("Request timed out after multiple attempts")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429: # Rate limit
print("Rate limit exceeded. Please wait or upgrade your plan.")
elif e.response.status_code == 401: # Unauthorized
print("Invalid API key. Please check your credentials.")
elif e.response.status_code == 400: # Bad request
print("Invalid currency codes or date format.")
else:
print(f"HTTP error: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
return None
Integration Examples
Flask Web App
Here's a simple web interface. For a more modern approach with React components, check out our React currency converter tutorial:
from flask import Flask, request, jsonify, render_template_string
app = Flask(__name__)
converter = CurrencyConverter("your_api_key_here")
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Currency Converter</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.form-group { margin: 15px 0; }
input, select, button { padding: 10px; margin: 5px; }
.result { margin-top: 20px; font-size: 1.2em; color: #2c3e50; }
</style>
</head>
<body>
<h1>Currency Converter</h1>
<form id="converter-form">
<div class="form-group">
<label>From:</label>
<select id="from-currency">
<option value="AUD">AUD</option>
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="GBP">GBP</option>
</select>
</div>
<div class="form-group">
<label>To:</label>
<select id="to-currency">
<option value="USD">USD</option>
<option value="AUD">AUD</option>
<option value="EUR">EUR</option>
<option value="GBP">GBP</option>
</select>
</div>
<div class="form-group">
<label>Amount:</label>
<input type="number" id="amount" step="0.01" value="100">
</div>
<button type="submit">Convert</button>
</form>
<div id="result" class="result"></div>
<script>
document.getElementById('converter-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch('/convert', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
from: document.getElementById('from-currency').value,
to: document.getElementById('to-currency').value,
amount: parseFloat(document.getElementById('amount').value)
})
});
const result = await response.json();
document.getElementById('result').innerHTML =
`${result.amount} ${result.from} = ${result.result.toFixed(2)} ${result.to}`;
});
</script>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/convert', methods=['POST'])
def convert_api():
data = request.get_json()
result = converter.convert_currency(
data['from'],
data['to'],
data['amount']
)
return jsonify({
'from': data['from'],
'to': data['to'],
'amount': data['amount'],
'result': result
})
if __name__ == '__main__':
app.run(debug=True)
Scheduled Data Updates
For applications that need regular updates:
import schedule
import sqlite3
from datetime import datetime
def store_daily_rates():
"""Store daily rates in local database"""
converter = CurrencyConverter("your_api_key_here")
rates_data = converter.get_latest_rates()
if rates_data:
conn = sqlite3.connect('exchange_rates.db')
cursor = conn.cursor()
# Create table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS daily_rates (
date TEXT PRIMARY KEY,
rates TEXT,
timestamp INTEGER
)
''')
# Insert today's rates
cursor.execute('''
INSERT OR REPLACE INTO daily_rates (date, rates, timestamp)
VALUES (?, ?, ?)
''', (
rates_data['date'],
str(rates_data['rates']),
int(datetime.now().timestamp())
))
conn.commit()
conn.close()
print(f"Stored rates for {rates_data['date']}")
# Schedule daily updates
schedule.every().day.at("16:30").do(store_daily_rates) # After RBA publishes (4 PM AEST)
# Run scheduler
while True:
schedule.run_pending()
time.sleep(60)
Why Use an API Instead of Scraping?
After building this converter, you might wonder why not just scrape the RBA website directly. Here's what you'd face:
- Reliability Issues: HTML structure changes break scrapers
- Rate Limits: RBA may block frequent requests
- Data Format: Converting HTML tables to usable data is complex
- Historical Data: Accessing past rates requires downloading Excel files
- Maintenance: You're responsible for monitoring and fixing breaks
The Exchange Rates API handles all of this, providing clean JSON responses with consistent formatting and comprehensive historical data.
Next Steps
This converter gives you a solid foundation for currency-related applications. You could extend it to:
- Build a portfolio tracker for international investments
- Create automated invoice generation with current rates
- Develop a travel budget calculator
- Integrate with accounting software for multi-currency support
For web-based applications, consider our React implementation with modern UI components. If you're working with WordPress sites, our WordPress plugin tutorial provides ready-to-use shortcodes and WooCommerce integration.
The official RBA data ensures compliance with Australian tax requirements, making it perfect for business applications.
We are not affiliated with or endorsed by the Reserve Bank of Australia.
For API documentation, visit Exchange Rates API Docs