How to Build a Real-Time Currency Converter in Python Using the RBA Exchange Rates API

Build a production-ready currency converter in Python using official Reserve Bank of Australia exchange rate data. Complete with error handling, historical rates, and practical examples you can use immediately.

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:

  1. Reliability Issues: HTML structure changes break scrapers
  2. Rate Limits: RBA may block frequent requests
  3. Data Format: Converting HTML tables to usable data is complex
  4. Historical Data: Accessing past rates requires downloading Excel files
  5. 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