If you have ever tried to pull fundamentals for Indian stocks programmatically, you know the pain: NSE sits behind Akamai and blocks anything that isn't a browser, BSE is worse, Screener.in needs a login for anything useful, and the paid vendors want an enterprise contract for what is essentially public data.
There is a quieter option. Tickertape renders its stock pages from a set of JSON endpoints that answer plain HTTP requests, no cookies, no token. I spent a day mapping them. This post documents what I found, with working Python, and then shows the hosted version I built for people who would rather not maintain scrapers.
The endpoints
All of these live on https://api.tickertape.in and only need a normal browser user agent and a Referer header.
Search a ticker or name to get Tickertape's internal id (sid):
import requests
H = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/128.0 Safari/537.36",
"Referer": "https://www.tickertape.in/",
}
r = requests.get("https://api.tickertape.in/search", params={"text": "reliance", "types": "stock"}, headers=H)
stocks = r.json()["data"]["stocks"]
print(stocks[0]["sid"], stocks[0]["ticker"], stocks[0]["slug"])
# RELI RELIANCE /stocks/reliance-industries-RELI
Key ratios and company info:
info = requests.get("https://api.tickertape.in/stocks/info/RELI", headers=H).json()["data"]
print(info["info"]["name"], info["ratios"]["pe"], info["ratios"]["mrktCapf"])
ratios includes P/E, P/B, EPS, book value, dividend yield, ROE, beta, 52-week range, market-cap rank and the industry averages for P/E, P/B and yield.
Quarterly shareholding history (promoter, pledged, FII, DII, mutual funds, retail):
holdings = requests.get("https://api.tickertape.in/stocks/holdings/RELI", headers=H).json()["data"]
for h in holdings:
d = h["data"]
print(h["date"][:10], "promoter", round(d["pmPctT"], 2), "FII", round(d["fiPctT"], 2), "MF", round(d["mfPctT"], 2))
Index constituents:
nifty = requests.get("https://api.tickertape.in/indices/constituents/.NSEI", headers=H).json()["data"]
print(len(nifty["constituents"]), nifty["constituents"][0]["ticker"])
.NSEI is NIFTY 50; .NSEBANK, .NIFTY500 and friends work the same way.
The screener is a POST with a filter object. g means greater than, l means less than:
body = {
"match": {"mrktCapf": {"g": 5000}, "apef": {"l": 20}, "divYield": {"g": 2}},
"sortBy": "divYield", "sortOrder": -1,
"project": ["subindustry", "mrktCapf", "lastPrice", "apef", "divYield", "pr1y"],
"offset": 0, "count": 50, "sids": [],
}
res = requests.post("https://api.tickertape.in/screener/query", json=body, headers=H).json()["data"]["results"]
for row in res[:5]:
print(row["stock"]["info"]["ticker"], row["stock"]["advancedRatios"]["divYield"])
The financial statements
The statements aren't on the API host. They're embedded in the stock page's Next.js payload. Fetch the page, pull the __NEXT_DATA__ script tag, and you get ten years of income statement, balance sheet and cash flow, plus quarterly results, dividends, corporate actions, analyst forecasts and the mutual funds holding the stock:
import json, re
html = requests.get("https://www.tickertape.in/stocks/reliance-industries-RELI", headers=H).text
data = json.loads(re.search(r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>', html, re.S).group(1))
props = data["props"]["pageProps"]
income = props["income-normal-annual"] # list of yearly rows
print(income[-1]["displayPeriod"], income[-1]["incTrev"], income[-1]["incNinc"])
Field names are codes (incTrev is total revenue, incNinc net income). The page also ships a labels object mapping every code to its display name under the frontL key, so you can translate them in one pass.
Things that bit me
- The screener returns at most 100 rows per call; paginate with
offset. - Statement values are in ₹ crore.
- The page payload is about 1.2 MB per stock. Cache it.
- Nothing here is documented or guaranteed. Tickertape can rename a field any day, and their terms of use apply.
If you'd rather not maintain this
I packaged all of the above as a pay-per-result actor on Apify, so the endpoint mapping is my problem, not yours: Tickertape India Stocks Scraper. It has three modes: a list of tickers, a whole index, or a screener query, and it returns the statements with readable field names. A full profile costs two cents; all of NIFTY 50 with ten years of financials is about a dollar. There are runnable examples in Python, Node and n8n in this repo.
If you're after IPO data instead, Chittorgarh and InvestorGain have similar hidden report endpoints; that mapping is here.
Questions or a field you need that isn't there? Comments are open.
Top comments (0)