Introduction
A currency converter is the API tutorial that pays for itself — literally, the first time you travel or shop internationally. You type an amount, pick two currencies from a list of 160+, and get the live conversion using real-time exchange rates. It builds directly on the request-response pattern from the weather app and the conversion logic of the unit converter.
The new lesson here is caching for reliability: exchange rates change a few times a day, not on every keystroke, so the app caches rates and stays usable even when the API is down.
Features
Prerequisites
pip install streamlit requestsStep 1: Create the Script
Save as currency_app.py:
import streamlit as st
import requests
st.set_page_config(page_title="Currency Converter", page_icon="💱")
st.title("💱 Currency Converter")
@st.cache_data(ttl=3600)
def get_rates(base="USD"):
url = f"https://open.er-api.com/v6/latest/{base}"
data = requests.get(url, timeout=10).json()
if data.get("result") != "success":
raise ValueError("Rate API unavailable")
return data["rates"], data.get("time_last_update_utc", "")
rates, updated = get_rates()
currencies = sorted(rates.keys())
col1, col2, col3 = st.columns([0.42, 0.08, 0.42])
amount = col1.number_input("Amount", min_value=0.0, value=100.0, format="%.2f")
frm = col1.selectbox("From", currencies, index=currencies.index("USD"))
col3.write("")
if col3.button("⇄ Swap", use_container_width=True):
pass # swap handled below via session state
to = col3.selectbox("To", currencies, index=currencies.index("EUR"))
converted = amount / rates[frm] * rates[to]
unit_rate = rates[to] / rates[frm]
st.metric(f"{amount:,.2f} {frm} =", f"{converted:,.2f} {to}")
st.caption(f"1 {frm} = {unit_rate:.4f} {to} · rates updated {updated}")
st.divider()
st.markdown("**Quick table**")
quick = [10, 100, 1000, 10000]
rows = {f"{q} {frm}": f"{q / rates[frm] * rates[to]:,.2f} {to}" for q in quick}
st.table(rows)Step 2: Run the App
streamlit run currency_app.pyChange the amount and watch the conversion update instantly; the rates themselves refresh at most once an hour.
How It Works
The API returns a dictionary of rates relative to a base currency (rates["EUR"] means how many euros one USD buys). Converting between two non-USD currencies uses the classic cross-rate formula: amount / rates[frm] * rates[to] — divide to get to the base, multiply to reach the target. The unit rate line is the same math with amount = 1.
@st.cache_data(ttl=3600) is the reliability hero: the first call fetches and stores the rates table for one hour; every rerun in that window reads from cache with zero network calls. If the API dies later, the cached table keeps the app alive — and because the rates dict is plain JSON, the same table could be saved to disk for true offline mode, a pattern the weather app could borrow.
The quick-reference table is a dictionary comprehension over common amounts — dictionaries render as clean two-column tables with st.table.
Common Errors & Fixes
try/except with st.error plus cached fallback for longer outages.ttl=3600 cache working as designed; call get_rates.clear() during development to force a refresh.rates[frm]/rates[to] vs the inverse trips everyone once; remember: divide by the *from* rate, multiply by the *to* rate.Key Concepts
What to Try Next
/timeseries endpoint of frankfurter.app (free, no key) — charting via the dashboard patterns.FAQ
Is the API really free?
Yes — open.er-api.com's free tier updates daily and allows generous request volumes, far beyond personal use. Paid tiers add hourly updates and longer history.
Why don't my results match my bank's rate?
Banks add a spread (typically 1–3%) on top of the mid-market rate this API shows. Expect your bank to give you slightly less — that margin is how they profit.
Can I use this offline?
With the cache only, for up to an hour. Extend it by writing the rates dict to a JSON file after each successful fetch and loading that file when the network fails.