Overview
The Surveillance Lists API retrieves NSE’s Additional Surveillance Measure (ASM) and GSM (Graded Surveillance Measure) lists. This endpoint uses a triple-fallback strategy: Google Sheets Gviz API (primary), Dhan Next.js API (secondary), and web scraping (tertiary) to ensure maximum reliability.
Source File: fetch_surveillance_lists.py
Endpoint Details
Primary Source: Google Sheets Gviz API
https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:json&gid={GID}
Secondary Source: Dhan Next.js API
https://dhan.co/_next/data/{BUILD_ID}/{PAGE_KEY}.json
Tertiary Source: Web Scraping
https://dhan.co/{PAGE_URL}/
GET (parse __NEXT_DATA__ script tag)
Configuration
ASM List
https://dhan.co/nse-asm-list/
GSM List
https://dhan.co/nse-gsm-list/
{
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
Example Requests
Primary: Google Sheets Gviz
curl -X GET "https://docs.google.com/spreadsheets/d/1zqhM3geRNW_ZzEx62y0W5U2ZlaXxG-NDn0V8sJk5TQ4/gviz/tq?tqx=out:json&gid=290894275" \
-H "User-Agent: Mozilla/5.0"
Secondary: Next.js API
# First get build ID
curl -X GET https://dhan.co/all-indices/ | grep buildId
# Then fetch data
curl -X GET "https://dhan.co/_next/data/{BUILD_ID}/nse-asm-list.json" \
-H "User-Agent: Mozilla/5.0"
Tertiary: Web Scraping
curl -X GET https://dhan.co/nse-asm-list/ \
-H "User-Agent: Mozilla/5.0"
Response Structure
List Object Fields
Surveillance stage (for ASM: “Stage 1”, “Stage 2”, etc.; for GSM: similar staging)
Example Response (Google Sheets Gviz)
{
"table": {
"rows": [
{
"c": [
null,
{"v": "YESBANK"},
{"v": "Yes Bank Ltd."},
{"v": "INE528G01035"},
{"v": "Stage 1"}
]
}
]
}
}
Processed Output
[
{
"Symbol": "YESBANK",
"Name": "Yes Bank Ltd.",
"ISIN": "INE528G01035",
"Stage": "Stage 1"
},
{
"Symbol": "SUZLON",
"Name": "Suzlon Energy Ltd.",
"ISIN": "INE040H01021",
"Stage": "Stage 2"
}
]
Implementation Details
Triple-Fallback Strategy
import requests
import json
import re
from bs4 import BeautifulSoup
def fetch_surveillance_lists():
spreadsheet_base_url = "https://docs.google.com/spreadsheets/d/1zqhM3geRNW_ZzEx62y0W5U2ZlaXxG-NDn0V8sJk5TQ4/gviz/tq?tqx=out:json&gid="
lists_config = {
"nse_asm_list.json": {
"gid": "290894275",
"web_url": "https://dhan.co/nse-asm-list/",
"data_key": "nse-asm-list"
},
"nse_gsm_list.json": {
"gid": "1525483995",
"web_url": "https://dhan.co/nse-gsm-list/",
"data_key": "nse-gsm-list"
}
}
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}
build_id = get_build_id() # Fetch dynamic build ID
for filename, config in lists_config.items():
gid = config['gid']
cleaned_list = []
success = False
# Attempt 1: Google Sheets Gviz API
try:
url = f"{spreadsheet_base_url}{gid}"
response = requests.get(url, headers=headers, timeout=10)
text = response.text
match = re.search(r'setResponse\((.*)\);', text)
if match:
data = json.loads(match.group(1))
rows = data.get('table', {}).get('rows', [])
for row in rows:
c = row.get('c', [])
if len(c) >= 5:
symbol = c[1].get('v') if c[1] else None
name = c[2].get('v') if c[2] else None
isin = c[3].get('v') if c[3] else None
stage = c[4].get('v') if c[4] else None
if symbol and symbol != "Symbol":
cleaned_list.append({
"Symbol": str(symbol),
"Name": str(name),
"ISIN": str(isin),
"Stage": str(stage)
})
success = True
except Exception as e:
print(f"Gviz failed: {e}")
# Attempt 2: Next.js API (if Gviz failed)
if not success and build_id:
try:
url = f"https://dhan.co/_next/data/{build_id}/{config['data_key']}.json"
response = requests.get(url, headers=headers)
# Parse pageProps for list data
success = True # If data found
except:
pass
# Attempt 3: Web scraping (if both failed)
if not success:
try:
response = requests.get(config['web_url'], headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
script = soup.find('script', id='__NEXT_DATA__')
if script:
data_json = json.loads(script.string)
# Extract list from props
success = True
except:
pass
if success:
with open(filename, "w") as f:
json.dump(cleaned_list, f, indent=4)
def get_build_id():
"""Dynamically fetch Next.js build ID"""
url = "https://dhan.co/all-indices/"
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(url, headers=headers, timeout=10)
match = re.search(r'"buildId":"([^"]+)"', response.text)
return match.group(1) if match else None
except:
return None
Output Files
nse_asm_list.json
[
{
"Symbol": "YESBANK",
"Name": "Yes Bank Ltd.",
"ISIN": "INE528G01035",
"Stage": "Stage 1"
},
{
"Symbol": "SUZLON",
"Name": "Suzlon Energy Ltd.",
"ISIN": "INE040H01021",
"Stage": "Stage 2"
}
]
nse_gsm_list.json
[
{
"Symbol": "EXAMPLE",
"Name": "Example Company Ltd.",
"ISIN": "INE123A01012",
"Stage": "Stage 1"
}
]
Surveillance Stages
ASM (Additional Surveillance Measure)
- Stage 1: Initial surveillance - price volatility, volume spikes
- Stage 2: Enhanced surveillance - continued concerns
- Stage 3: Strictest surveillance - severe concerns
- Long Term: Long-term ASM list
- Short Term: Short-term ASM list
GSM (Graded Surveillance Measure)
- Stage 1: Initial graded surveillance
- Stage 2: Enhanced graded surveillance
- Stage 3+: Higher surveillance stages
Use Cases
- Risk Screening: Filter out high-risk surveillance stocks
- Compliance Checking: Ensure portfolio doesn’t include restricted stocks
- Intraday Trading: ASM stocks have stricter intraday limits
- Position Limits: GSM stocks have position size restrictions
- Circuit Limits: Surveillance stocks often have tighter circuit filters
- ASM List Size: 50-150 stocks (varies)
- GSM List Size: 20-80 stocks (varies)
- Fetch Time: 2-5 seconds per list
- Success Rate: >99% (triple fallback)
- Update Frequency: NSE updates weekly/as needed
Trading Restrictions
Stocks in surveillance lists face:
- Reduced Circuit Limits: 5% or 2% instead of 10%/20%
- Trade-for-Trade (T2T): No intraday allowed
- 100% Margin: Full upfront payment required
- Position Limits: Maximum holding restrictions
- Auction Settlement: Delivery obligations strictly enforced
Error Handling
Primary Source Failure
try:
# Google Sheets Gviz
response = requests.get(gviz_url, timeout=10)
# Process...
except Exception as e:
print(f"Gviz failed: {e}")
# Fall back to secondary source
All Sources Failed
if not success:
print(f"Critical failure: Could not fetch {filename} from any source.")
# No file written
Data Validation
# Skip header rows
if symbol == "Symbol" or not symbol:
continue
# Ensure all fields are strings
cleaned_list.append({
"Symbol": str(symbol),
"Name": str(name),
"ISIN": str(isin),
"Stage": str(stage)
})
Integration Example
# Load surveillance lists
with open("nse_asm_list.json", "r") as f:
asm_list = json.load(f)
with open("nse_gsm_list.json", "r") as f:
gsm_list = json.load(f)
# Create lookup sets
asm_symbols = {stock["Symbol"] for stock in asm_list}
gsm_symbols = {stock["Symbol"] for stock in gsm_list}
# Filter master list
safe_stocks = [
stock for stock in master_list
if stock["Symbol"] not in asm_symbols and stock["Symbol"] not in gsm_symbols
]
Notes
- Surveillance lists change frequently - run daily for accuracy
- Google Sheets is the most reliable source (99%+ uptime)
- Build ID changes with Next.js deployments - extracted dynamically
- Web scraping is slowest but most resilient fallback
- Stage progression: stocks can move up/down or be removed
- Exit from surveillance: stocks meeting criteria for 6+ months may be removed
- ASM is more common than GSM
- Both lists are maintained by NSE and updated on exchange website