Code Examples
Django 4.2 with CurrencyCore
A cached Django view that calls CurrencyCore with requests, propagates status, and keeps the key in server settings.
Django already has the two pieces this integration needs: a settings module for secrets and a cache framework for dated snapshots. The view below reads the key from the environment at startup, calls /v1/rates/{base} with requests, forwards the upstream status, and caches successful bodies by base and date.
The example targets Django 4.2 or later with requests installed. Free includes 1,000 requests per month at 60 per minute. Test keys use the same reference data and quota and are capped at 60 per minute. Keep the key in the environment, not in settings.py itself.
# settings.py
import os
CURRENCYCORE_API_KEY = os.environ.get("CURRENCYCORE_API_KEY", "")
CURRENCYCORE_BASE_URL = "https://api.currency-core.com/v1"
CURRENCYCORE_TIMEOUT = 10
# views.py
from datetime import datetime, timezone
import requests
from django.conf import settings
from django.core.cache import cache
from django.http import JsonResponse
from django.views.decorators.http import require_GET
def error(code, message, status):
return JsonResponse({"error": {"code": code, "message": message}}, status=status)
@require_GET
def rates(request, base="USD"):
if not settings.CURRENCYCORE_API_KEY:
return error("internal_error", "CURRENCYCORE_API_KEY is not set", 500)
base = base.upper()
day = request.GET.get("date", "")
if len(base) != 3 or not base.isalpha():
return error("invalid_input", "base must be a three-letter code", 400)
if day and not (len(day) == 10 and day[4] == "-" and day[7] == "-"):
return error("invalid_input", "date must be YYYY-MM-DD", 400)
cache_key = f"cc:rates:{base}:{day or 'latest'}"
cached = cache.get(cache_key)
if cached is not None:
return JsonResponse(cached)
try:
upstream = requests.get(
f"{settings.CURRENCYCORE_BASE_URL}/rates/{base}",
params={"date": day} if day else None,
headers={"Authorization": f"Bearer {settings.CURRENCYCORE_API_KEY}"},
timeout=settings.CURRENCYCORE_TIMEOUT,
)
except requests.RequestException:
return error("internal_error", "CurrencyCore request failed", 502)
try:
payload = upstream.json()
except ValueError:
return error(
"internal_error",
f"Invalid JSON from upstream (HTTP {upstream.status_code})",
502,
)
if upstream.status_code >= 400:
return JsonResponse(payload, status=upstream.status_code)
is_today = payload.get("date") == datetime.now(timezone.utc).date().isoformat()
cache.set(cache_key, payload, 300 if is_today else 86_400)
return JsonResponse(payload)
The payload is returned unchanged, so the base stays at 1 and the date is the rate date, not necessarily the one requested:
{
"base": "EUR",
"date": "2026-11-10",
"rates": { "EUR": 1, "USD": 1.0861, "INR": 91.62 }
}
Wire both the default and the rebased path:
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("api/rates/", views.rates, name="rates"),
path("api/rates/<str:base>/", views.rates, name="rates-base"),
]
A 403 for a historical date outside the Free 30-day window reaches the client with status 403 and the original error.code. Do not convert every failure into 200.
Production notes
- Never pass the key into a template context, a
data-attribute, or client JavaScript. Django templates render HTML, and anything rendered is public. LocMemCacheis per process. Use Redis or Memcached when several workers must share entries.- Cache by base and requested date, as above. A response for a past date is stable for normal caching; today’s entry should expire quickly because the feed polls hourly and the underlying fixings are daily.
- Set
CURRENCYCORE_TIMEOUTlow enough to protect a web worker, and translaterequests.Timeoutinto502, not500. - If your SPA runs on another origin, allow only that origin in CORS. The key still stays on the Django server.
Next steps
- API reference and authentication
- Error codes and rate limits
- Observability guide for logging and timing
- All developer guides