# Add currency exchange rates and conversion to Python

Use requests plus a time-based cache for current rates. Expire the cached response at nextUpdateAt to save monthly quota.

## Get a free API key

Sign up at https://currencyexchangerate-api.com/auth/login, then create a key under API keys in the console. The free plan includes 3,000 requests per month with no credit card. Store the secret as CXR_API_KEY in server-side environment configuration; never expose it in browser code.

File: `.env`

```shell
CXR_API_KEY=cxr_your_secret_key
```

## Create a reusable client and cache rates

Use the standard HTTP client. Cache every /latest response until nextUpdateAt: rates change at most once by then, and reusing the response saves quota. The helper also exposes /convert.

File: `rates.py`

```python
import os
import time
from datetime import datetime

import requests

API_BASE_URL = 'https://api.currencyexchangerate-api.com/api/v1'
HEADERS = {'Authorization': 'Bearer ' + os.environ['CXR_API_KEY']}
_cache = {}


def latest(base='USD', symbols='EUR,GBP'):
    key = (base, symbols)
    if key in _cache and time.time() < _cache[key][0]:
        return _cache[key][1]
    response = requests.get(
        API_BASE_URL + '/latest',
        params={'base': base, 'symbols': symbols},
        headers=HEADERS,
    )
    response.raise_for_status()
    data = response.json()
    next_update = data['nextUpdateAt'].replace('Z', '+00:00')
    until = datetime.fromisoformat(next_update).timestamp()
    _cache[key] = (until, data)
    return data


def convert(source, target, amount):
    response = requests.get(
        API_BASE_URL + '/convert',
        params={'from': source, 'to': target, 'amount': amount},
        headers=HEADERS,
    )
    response.raise_for_status()
    return response.json()
```

## Use it in Python

Call the helper from a server route, controller, view, command, or script. Format converted values for the user’s locale.

File: `app.py`

```python
from rates import convert, latest

rates = latest('USD', 'EUR,GBP')
result = convert('EUR', 'GBP', 100)
print(f"{result['result']:,.2f} {result['to']}")
```

## Handle API errors

A 401 means the key is invalid. A 429 can mean a rate limit or quota limit; read Retry-After in seconds before retrying. Other errors use application/problem+json.

File: `errors.py`

```python
import requests

from rates import convert

try:
    result = convert('EUR', 'GBP', 100)
except requests.HTTPError as error:
    response = error.response
    if response.status_code == 401:
        raise RuntimeError('Check CXR_API_KEY') from error
    if response.status_code == 429:
        retry_after = response.headers.get('Retry-After', '?')
        raise RuntimeError(f'Retry after {retry_after} seconds') from error
    detail = response.json().get('detail', 'Rates API error')
    raise RuntimeError(detail) from error
```

## Format the amount for display

Use locale-aware currency formatting instead of adding a symbol manually.

File: `format.py`

```python
# pip install babel
from babel.numbers import format_currency

print(format_currency(85.249105, 'EUR', locale='en_GB'))
```

## Related links

- [API documentation](https://currencyexchangerate-api.com/docs.md)
- [All integration guides](https://currencyexchangerate-api.com/guides.md)
- [Get a free API key](https://currencyexchangerate-api.com/auth/login)
