EZRate
Sign in

USD to IRR API in Python and Django: Fetch, Cache and Display Live Rates

Use a USD to IRR API in Python: fetch the free-market rate with requests, add retries and caching, build a Django view and avoid Rial/Toman bugs. Code included.

Ersan Fazli
· Web Developer
Published 24 October 2026, 20:00 · 4 min read
USD to IRR API in Python and Django
Key takeaways
  • A small Python client with a session, timeout and retries is enough to read live rates from the EZRate API.
  • In Django, wrap the call in the cache framework so every visitor is served from one cached response.
  • Store raw integers, keep the unit next to the value and format only at display time.

This tutorial builds a production-ready integration of the USD to IRR API in Python: a reusable client, caching, a Django view and a template that shows buy/sell rates with their update time. Everything uses the Tehran free-market rate, not the official one.

★
Short answer
Install requests, read the key from an environment variable, call GET https://api.ezrate.me/v1/rates?symbols=usd with an X-API-Key header and a timeout, and cache the JSON for meta.freshness_sec seconds with Django’s cache framework. Pass quote=IRR if your system works in Rial.

Part of the Iranian Rial exchange rate API guide. If you have no key yet, follow the quickstart.

1. Set up

bash
python3 -m venv .venv && source .venv/bin/activate
pip install requests django
export EZRATE_KEY="your-key-here"   # never commit it

2. A small, robust client

python
# ezrate.py
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

BASE = "https://api.ezrate.me/v1"

class EZRate:
    def __init__(self, key: str | None = None):
        self.s = requests.Session()
        self.s.headers["X-API-Key"] = key or os.environ["EZRATE_KEY"]
        retry = Retry(total=3, backoff_factor=0.5,
                      status_forcelist=[502, 503, 504],
                      respect_retry_after_header=True)
        self.s.mount("https://", HTTPAdapter(max_retries=retry))

    def get(self, path: str, **params) -> dict:
        r = self.s.get(f"{BASE}{path}", params=params, timeout=10)
        if r.status_code == 429:
            raise RuntimeError(f"rate limited, retry after {r.headers.get('Retry-After')}s")
        r.raise_for_status()
        return r.json()

    def rates(self, symbols: list[str], quote: str = "IRT") -> dict:
        body = self.get("/rates", symbols=",".join(symbols), quote=quote, lang="en")
        return {x["symbol"]: x for x in body["data"]}, body["meta"]

We do not retry 429 automatically: hitting your quota usually means the caching is wrong, and blind retries make it worse. Error codes are listed in the JSON reference.

3. Try it

python
from ezrate import EZRate

rates, meta = EZRate().rates(["usd", "eur", "aed", "usdt"])
usd = rates["usd"]
print(f"USD buy {usd['buy']:,} / sell {usd['sell']:,} {usd['quote']}")
print("sources:", usd["sources"], "low:", usd["low_sources"], "as of", meta["as_of"])

4. Cache it in Django

python
# rates/services.py
from django.core.cache import cache
from ezrate import EZRate

KEY = "ezrate:rates:v1"

def live_rates():
    hit = cache.get(KEY)
    if hit:
        return hit
    try:
        rates, meta = EZRate().rates(["usd", "eur", "aed", "try", "g18", "emami"])
    except Exception:
        stale = cache.get(KEY + ":last")
        if stale:
            return stale | {"stale": True}
        raise
    data = {"rates": rates, "meta": meta, "stale": False}
    ttl = max(30, int(meta.get("freshness_sec", 60)))
    cache.set(KEY, data, ttl)
    cache.set(KEY + ":last", data, 24 * 3600)   # fallback copy
    return data

Two keys: a short-lived one for normal reads and a long-lived “last good” copy you can serve, marked as stale, if the API is unreachable. Use Redis or Memcached as the cache backend in production. The same pattern in PHP is in Laravel exchange rate API caching.

5. View and template

python
# rates/views.py
from django.shortcuts import render
from .services import live_rates

def board(request):
    return render(request, "rates/board.html", live_rates())
html
<!-- rates/templates/rates/board.html -->
<table>
  <tr><th>Asset</th><th>Buy</th><th>Sell</th><th>Change</th></tr>
  {% for sym, r in rates.items %}
  <tr>
    <td>{{ r.name }}</td>
    <td>{{ r.buy|floatformat:0 }}</td>
    <td>{{ r.sell|floatformat:0 }}</td>
    <td>{{ r.change_pct }}%</td>
  </tr>
  {% endfor %}
</table>
<p>Prices in {{ meta.quote }} · updated {{ meta.as_of }}{% if stale %} · showing last known rates{% endif %}</p>
Rendered Django page with a live USD, EUR, AED, TRY, gold and coin table
The finished Django rate board

6. Historical candles with pandas

python
import pandas as pd
from ezrate import EZRate

body = EZRate().get("/history/usd", interval="1d", **{"from": "2026-01-01", "to": "2026-10-01"})
df = pd.DataFrame(body["data"])
df["t"] = pd.to_datetime(df["t"], utc=True)   # ISO 8601, UTC
df = df.set_index("t")
print(df["c"].resample("W").last().pct_change().describe())

Candles are based on the sell rate and use UTC. Research workflows are covered in historical USD/IRR data.

Rial, Toman and Decimal

  • Prefer quote=IRR over multiplying by 10 yourself.
  • Rates in Toman/Rial are integers; for crypto in USDT use Decimal, not float, in money logic.
  • Store the unit in the same row as the amount.

More in IRR vs Toman for developers.

US Dollar271,860Toman+0.70%

How the pieces fit together

The client, the cache and the view each have one job. The client knows how to talk to the API: base URL, key, timeout and which errors are worth retrying. The service layer knows how fresh data must be and what to do when the upstream is unavailable. The view only formats numbers that are already in the cache. Keeping these responsibilities separate makes the integration easy to test and easy to change — for example, swapping Django’s local-memory cache for Redis in production is a one-line settings change.

Running it in production

  • Cache backend: use Redis (django-redis) or Memcached so every worker shares one copy; the local-memory cache is per process.
  • Warm the cache: a Celery beat task or a cron-driven management command that calls live_rates() every minute means no user request ever waits for the API.
  • Timeouts: keep the request timeout well below your web server’s timeout so a slow upstream cannot block workers.
  • Observability: log the age of the cached as_of and alert when it exceeds a few minutes.
  • Secrets: read EZRATE_KEY from the environment or a secrets manager; never put it in settings.py under version control.

Testing without the network

Mock the HTTP layer with responses or requests-mock, feed in a recorded JSON body and assert on what your view renders. Add one test where the API returns 503 and verify that the page still shows the last cached rates with the stale notice. These two tests catch most real-world regressions.

python
import responses
from rates.services import live_rates

@responses.activate
def test_live_rates_uses_cache_on_failure(settings):
    responses.get("https://api.ezrate.me/v1/rates", status=503)
    # prime the fallback copy first in your fixture, then:
    data = live_rates()
    assert data["stale"] is True
Django admin or logs showing cache age and the last successful API refresh
Watching cache freshness in production

Related live rates: US dollar rate, 18K gold price, Emami coin price.

How do I get the USD to IRR rate in Python?
Send a GET request to https://api.ezrate.me/v1/rates/usd with your key in the X-API-Key header; add quote=IRR for Rial.
How often should my Django app call the API?
No more often than meta.freshness_sec. Cache the response and serve every visitor from the cache.
What happens if the API is down?
Serve the last good cached copy, marked as stale, and retry later.
Can I get historical data in pandas?
Yes, /history/{symbol} returns OHLC candles you can load straight into a DataFrame.
Ersan Fazli
I work in software development and programming, with a strong interest in building practical and efficient solutions. My focus is on developing new features, improving system performance, and solving technical challenges using modern technologies. I strive to create reliable, scalable, and high-quality software through continuous learning and the application of modern development practices.
All posts by author →

Comments (0)

Comments are shown after review. Sign-in is needed to send.

Related

API & Developers
Iran Gold Price API: Live 18K Gold per Gram, Mesghal, Ounce and Emami Coin Prices
API & Developers
Difference Between IRR and Toman: A Developer’s Guide to Units and Formatting
API & Developers
Currency API JSON Schema: EZRate Endpoints, Headers and Error Codes