You can fetch Indian mandi prices in Python without managing an API key when your use case fits this API's five supported states. The crucial extra step is checking the arrival_date: a successful HTTP response can still contain an older market report.
Install and run
Install Requests with python -m pip install requests, then run this Python 3.10+ example. It requests onion prices for Maharashtra and prints up to ten rows from the newest date in the response.
from datetime import date, datetime
from zoneinfo import ZoneInfo
from math import isfinite
import requests
url = "https://mandi-api.onrender.com/v1/prices"
params = {"state": "Maharashtra", "commodity": "Onion"}
try:
response = requests.get(url, params=params, timeout=(5, 45))
response.raise_for_status()
body = response.json()
except requests.RequestException as exc:
raise SystemExit(f"Price request failed: {exc}")
except ValueError:
raise SystemExit("The server did not return valid JSON")
if not isinstance(body, dict) or body.get("success") is not True:
raise SystemExit("Unexpected API response")
rows = body.get("data")
if not isinstance(rows, list):
raise SystemExit("Missing price rows")
dated_rows = []
for row in rows:
if not isinstance(row, dict):
continue
try:
reported_on = date.fromisoformat(row["arrival_date"])
price = float(row["modal_price"])
except (KeyError, TypeError, ValueError):
continue
if not isfinite(price) or price <= 0:
continue
dated_rows.append((reported_on, row))
if not dated_rows:
raise SystemExit("No dated prices match this query")
latest = max(day for day, _ in dated_rows)
today_ist = datetime.now(ZoneInfo("Asia/Kolkata")).date()
age_days = (today_ist - latest).days
if age_days < 0 or age_days > 3:
raise SystemExit(f"Latest report is {latest}; do not call it today's price")
print(f"Latest reported date: {latest} ({age_days} day(s) old)")
latest_rows = [row for day, row in dated_rows if day == latest]
for row in latest_rows[:10]:
print(row.get("market", "Unknown market"),
row.get("variety"), row["modal_price"], "INR/quintal")
print(f"Showing {min(10, len(latest_rows))} of {len(latest_rows)} latest rows")The three-day age check is an example policy, not a guarantee that every mandi reports daily. Set your own limit for the decision your app supports. The API returns at most 200 rows for /v1/prices, ordered by arrival date; this output is a recent sample, not a complete state-wide export.
Filter to a market or another crop
Change params to include a market name, for example {"state": "Maharashtra", "commodity": "Onion", "market": "Pune"}. The market filter is a partial-text match, so inspect the returned market and variety fields before displaying one value as a specific mandi's price. The API docs list the other filters.
curl -X GET "https://mandi-api.onrender.com/v1/prices?state=Maharashtra&commodity=Onion&market=Pune" \
-H "Accept: application/json"Handle the cases a demo often misses
- Timeouts and server errors: the example sets connect and read timeouts and checks the HTTP status before parsing JSON. A cold backend can take longer on the first request.
- Rate limit: the API allows 100 requests per 15 minutes per IP. If you get HTTP 429, back off instead of retrying immediately.
- No matching records: an empty list is not a price of zero. Ask the user to change the crop, state, market or date.
- Missing or old dates: reject malformed dates and label old reports clearly.
latest_fetched_atis when this API fetched a row, whilearrival_dateis the market report date. - Several varieties or markets: don't average them silently or label the first row “the onion price.” Show the market, variety and reporting date with each value.
This example uses the API's modal_price, reported in rupees per quintal. For trends rather than recent rows, use /v1/prices/history; its state-wide output is an average across reporting rows, not a single market quote. Try it in the playground, and read the freshness guide before putting a rate into an alert or farmer-facing answer.
