Skip to main content

Overview

The Company Filings API uses a hybrid dual-endpoint approach to maximize coverage of regulatory filings. It fetches from both the legacy /company_filings endpoint and the newer /lodr endpoint, then merges and deduplicates results to ensure comprehensive filing coverage. Source File: fetch_company_filings.py

Endpoint Details

Primary Endpoint

URL
string
required
https://ow-static-scanx.dhan.co/staticscanx/company_filings

Secondary Endpoint (LODR)

URL
string
required
https://ow-static-scanx.dhan.co/staticscanx/lodr
Method
string
required
POST (both endpoints)
Content-Type
string
required
application/json

Request Headers

{
  "Content-Type": "application/json",
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  "Accept": "application/json, text/plain, */*"
}

Request Payload

Both endpoints use identical payload structure:
data.isin
string
required
ISIN code of the company
data.pg_no
integer
default:"1"
Page number (1-indexed)
data.count
integer
default:"100"
Number of filings per page (maximum 100)

Example Payload

{
  "data": {
    "isin": "INE002A01018",
    "pg_no": 1,
    "count": 100
  }
}

Example Request

# Primary endpoint
curl -X POST https://ow-static-scanx.dhan.co/staticscanx/company_filings \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "isin": "INE002A01018",
      "pg_no": 1,
      "count": 100
    }
  }'

# Secondary endpoint (LODR)
curl -X POST https://ow-static-scanx.dhan.co/staticscanx/lodr \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "isin": "INE002A01018",
      "pg_no": 1,
      "count": 100
    }
  }'

Response Structure

data
array
Array of filing objects

Filing Object Fields

news_id
string
Unique filing identifier
news_date
string
Filing date in YYYY-MM-DD format
caption
string
Filing title/caption
descriptor
string
Filing description
file_url
string
PDF or document URL
category
string
Filing category (e.g., “Board Meeting”, “Financial Results”)

Example Response

{
  "data": [
    {
      "news_id": "12345",
      "news_date": "2024-01-15",
      "caption": "Outcome of Board Meeting",
      "descriptor": "Board approved Q3 FY24 results",
      "file_url": "https://example.com/filing.pdf",
      "category": "Board Meeting"
    }
  ]
}

Hybrid Merge Logic

The implementation merges results from both endpoints and deduplicates:

Deduplication Strategy

Primary Key
string
news_id (if available)
Fallback Key
string
{news_date}_{caption} (composite key)

Priority Rules

  1. If news_id exists, use it as unique key
  2. Otherwise, create composite key from date + caption
  3. On duplicates, prefer entry with file_url
  4. Sort final results by news_date descending (latest first)

Code Implementation

import requests
import json

def fetch_filings(isin):
    url1 = "https://ow-static-scanx.dhan.co/staticscanx/company_filings"
    url2 = "https://ow-static-scanx.dhan.co/staticscanx/lodr"
    
    payload = {"data": {"isin": isin, "pg_no": 1, "count": 100}}
    
    # Fetch from both endpoints
    data1 = requests.post(url1, json=payload, headers=headers, timeout=10).json().get("data", [])
    data2 = requests.post(url2, json=payload, headers=headers, timeout=10).json().get("data", [])
    
    # Merge and deduplicate
    combined = data1 + data2
    unique_map = {}
    
    for entry in combined:
        nid = entry.get("news_id")
        date_str = entry.get("news_date")
        caption = entry.get("caption") or entry.get("descriptor") or "Unknown"
        
        # Create unique key
        key = nid if nid else f"{date_str}_{caption}"
        
        # Prefer entries with file_url
        if key not in unique_map:
            unique_map[key] = entry
        elif entry.get("file_url") and not unique_map[key].get("file_url"):
            unique_map[key] = entry
    
    # Sort by date descending
    final_list = sorted(unique_map.values(), key=lambda x: x.get("news_date", "1900-01-01"), reverse=True)
    
    return {"code": 0, "data": final_list}

Implementation Details

Configuration

Max Threads
integer
default:"20"
Concurrent threads for parallel fetching across stocks
Timeout
integer
default:"10"
Request timeout in seconds per endpoint
Page Size
integer
default:"100"
Filings per page (maximum supported)
Force Update
boolean
default:"true"
Refresh all filings regardless of existing files
Input File
string
required
master_isin_map.json
Output Directory
string
company_filings/{SYMBOL}_filings.json

Output File Structure

Each stock gets its own file:
company_filings/
├── RELIANCE_filings.json
├── TCS_filings.json
└── HDFCBANK_filings.json
File format:
{
  "code": 0,
  "data": [
    {
      "news_id": "12345",
      "news_date": "2024-01-15",
      "caption": "Outcome of Board Meeting",
      "descriptor": "Board approved Q3 FY24 results",
      "file_url": "https://example.com/filing.pdf",
      "category": "Board Meeting"
    }
  ]
}

Threading Model

The script uses ThreadPoolExecutor for parallel processing:
from concurrent.futures import ThreadPoolExecutor, as_completed

MAX_THREADS = 20

with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
    future_to_stock = {executor.submit(fetch_filings, item): item["Symbol"] 
                       for item in stock_list}
    
    for future in as_completed(future_to_stock):
        result = future.result()
        # Process result

Performance Metrics

  • Total Stocks: ~2,775
  • Threads: 20 concurrent requests
  • Time per Stock: ~2-3 seconds (dual endpoint fetch)
  • Total Time: ~7-10 minutes for full market
  • Success Rate: >95%
  • Avg Filings per Stock: 50-100 (varies by company)

Progress Tracking

[100/2775] | Success: 95 | Skipped: 3 | Errors: 2 | Elapsed: 45.2s
[200/2775] | Success: 192 | Skipped: 5 | Errors: 3 | Elapsed: 89.1s

Error Handling

Timeout Handling

try:
    res = requests.post(url, json=payload, headers=headers, timeout=10)
    if res.status_code == 200:
        data = res.json().get("data", [])
except:
    data = []  # Graceful fallback

Empty Response Handling

  • If both endpoints return empty: File marked as “empty”
  • If one endpoint fails: Uses data from successful endpoint
  • If both fail: Error count incremented, no file written

Use Cases

  1. Regulatory Compliance Tracking: Monitor LODR filings
  2. Corporate Action Discovery: Find dividend, bonus, split announcements
  3. Board Meeting Outcomes: Track quarterly result approvals
  4. Material Events: Identify significant corporate events
  5. Document Archive: Build comprehensive filing database

Filing Categories

Common filing types retrieved:
  • Board Meeting Outcomes
  • Financial Results (Quarterly/Annual)
  • Dividend Announcements
  • Corporate Actions (Bonus, Split, Rights)
  • Material Events
  • Shareholding Pattern
  • Related Party Transactions
  • General Announcements

Notes

  • The dual-endpoint approach ensures maximum filing coverage
  • LODR endpoint typically has more recent filings
  • Legacy endpoint may have historical filings not in LODR
  • Deduplication prevents double-counting of filings
  • Files are per-symbol for efficient selective updates
  • Set FORCE_UPDATE = False to skip existing files (faster incremental runs)
  • The API does not paginate beyond page 1 in current implementation (100 filings assumed sufficient)

Build docs developers (and LLMs) love