Skip to main content

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

URL
string
required
https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:json&gid={GID}
Method
string
required
GET

Secondary Source: Dhan Next.js API

URL
string
required
https://dhan.co/_next/data/{BUILD_ID}/{PAGE_KEY}.json
Method
string
required
GET

Tertiary Source: Web Scraping

URL
string
required
https://dhan.co/{PAGE_URL}/
Method
string
required
GET (parse __NEXT_DATA__ script tag)

Configuration

ASM List

Spreadsheet GID
string
290894275
Web URL
string
https://dhan.co/nse-asm-list/
Data Key
string
nse-asm-list
Output File
string
nse_asm_list.json

GSM List

Spreadsheet GID
string
1525483995
Web URL
string
https://dhan.co/nse-gsm-list/
Data Key
string
nse-gsm-list
Output File
string
nse_gsm_list.json

Request Headers

{
  "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

Symbol
string
Trading symbol
Name
string
Company name
ISIN
string
ISIN code
Stage
string
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)

Build ID Extraction

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

  1. Risk Screening: Filter out high-risk surveillance stocks
  2. Compliance Checking: Ensure portfolio doesn’t include restricted stocks
  3. Intraday Trading: ASM stocks have stricter intraday limits
  4. Position Limits: GSM stocks have position size restrictions
  5. Circuit Limits: Surveillance stocks often have tighter circuit filters

Performance Metrics

  • 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

Build docs developers (and LLMs) love