DocsIntegration

Use exchange rates in a Django app

Cache the EUR to JPY rate and return a JSON conversion

30 Oct 2026 · 3 min read

Fetch the EUR to JPY rate with a cached helper, then multiply the requested amount in a Django view. Use Decimal for the calculation and round half up to whole yen. The helper keeps the rate in Django's cache for 300 seconds.

Install the packages and set your API key

Run the shell commands to install Django and requests, then set EXCHANGERATE_API_KEY in the environment. The helper reads that variable and sends its value in the Authorization header as a Bearer token.

Use the same environment variable name as the example. The request code reads it directly when it needs to fetch a rate, so the variable must be available to the app.

Shell
pip install django requestsexport EXCHANGERATE_API_KEY="your_key"

Add the cached rate helper

Put the helper in shop/rates.py. The view calls it with EUR as the base and JPY as the quote. On a cache miss, it sends GET /v1/latest/EUR?symbols=JPY to https://api.exchangerate.dev with a 5 second timeout.

The helper raises on an HTTP error before it reads the response. It takes the JPY value from rates and caches that value for 300 seconds. The cache key includes both currency codes. A second request inside that period reads the cached rate with no network call.

shop/rates.py
import osimport requestsfrom django.core.cache import cacheAPI = "https://api.exchangerate.dev/v1/latest"def get_rate(base: str, quote: str) -> float:    key = f"fx:{base}:{quote}"    rate = cache.get(key)    if rate is None:        response = requests.get(            f"{API}/{base}",            params={"symbols": quote},            headers={"Authorization": f"Bearer {os.environ['EXCHANGERATE_API_KEY']}"},            timeout=5,        )        response.raise_for_status()        rate = response.json()["rates"][quote]        cache.set(key, rate, 300)    return rate

Validate the amount and calculate whole yen

Put the conversion view in shop/views.py. It reads the amount query parameter, with a default of 1, and requires a finite number above zero. An invalid amount returns HTTP 400 with the error message amount must be a positive number.

The calculation uses Decimal for both the amount and the rate. The view converts the helper's rate through its string representation, multiplies it by the amount, then rounds half up to whole yen because JPY has no minor unit. Rounding happens after multiplication; the view does not round the rate first.

shop/views.py
from decimal import ROUND_HALF_UP, Decimalfrom django.http import JsonResponsefrom .rates import get_ratedef convert(request):    rate = Decimal(str(get_rate("EUR", "JPY")))    try:        amount = Decimal(request.GET.get("amount", "1"))        if not amount.is_finite() or amount <= 0:            raise ValueError        converted = (amount * rate).quantize(Decimal("1"), rounding=ROUND_HALF_UP)    except (ArithmeticError, ValueError):        return JsonResponse({"error": "amount must be a positive number"}, status=400)    return JsonResponse({"amount": str(amount), "rate": str(rate), "jpy": int(converted)})

Read the conversion response

The example response is the view's JSON for the sample request. amount is the EUR amount as a string, rate is the JPY per EUR rate as a string, and jpy is the converted amount as a whole number.

Multiply the EUR amount by the rate to get the yen amount before rounding. The jpy field contains the result after half-up rounding. When the helper uses a cached rate, the view still calculates the conversion from the amount in the current request.

Response of /convert?amount=1000
{"amount": "1000", "rate": "175.604", "jpy": 175604}

Check the rate's age separately from the cache

The 300 second cache period controls how long the helper reuses a rate. The API's data_updated_at field identifies the oldest contributing observation. A new fetch can therefore return a rate whose observation predates that request.

The API response also has result, base, source, sources, market_session, timestamp, effective_at, rates, stale, derived_symbols and notice. sources identifies the source per symbol, and stale is an object per symbol. The example helper retains only the requested rate, so the conversion JSON contains no API timestamps or per-symbol stale information.

Use the result as an indicative conversion

The calculated yen amount uses indicative mid-market figures. These rates are not for settlement or trading. Banks, cards and transfer services apply their own spread and fees, so the calculation does not establish a payout amount.

Whole-yen rounding follows JPY's lack of a minor unit. That rounding determines how the view expresses its calculated result. It does not account for a bank's, card's or transfer service's spread or fees.

Questions

Can I call the API without a key?

The API allows keyless requests at 10 requests a minute and 100 an hour per IP, with rates 15 minutes late. The helper in this example requires `EXCHANGERATE_API_KEY` because it reads the environment variable directly and sends a Bearer header.

How fresh are rates with a free key?

Free key rates are 15 minutes late; paid plans are live. A free key has 5,000 requests a month and 10 requests a minute. The helper's cache period is separate from that rate delay.

Which amounts return an error?

An amount that is not a finite number above zero returns HTTP 400 with an error message. If the query parameter is absent, the view uses its default amount. The view fetches the rate before it validates the amount.

Does every conversion make an API request?

A cache miss causes the helper to fetch the rate. A second request inside the 300 second cache period reuses that rate with no network call, and the view calculates the requested amount from it.

Keep reading

Get a free API key

Free has 5,000 requests a month and 10 requests a minute. No credit card is required.

Get a free key