Retrieve read-only corporate filing announcements for one or more listed equities over an inclusive date range, including filing text, structured fields, and attachment links. Keep the scrape narrow to the requested public endpoint or one rendered public page; stop if authentication, CAPTCHA, or another access control is required.
Use Cases
Use for requests to research, list, verify, or review NSE India corporate filings or announcements for specified equity symbols and date ranges. For multiple NSE symbols, apply the same date-filtering extractor independently to each symbol URL and return results grouped by symbol. For BSE-specific requests, use the optional BSE announcements API route when a BSE scrip code is available.
Automation Flow
- Prefer the direct NSE corporate-announcements JSON endpoint, URL-encoding the symbol and formatting dates as
DD-MM-YYYY:
https://www.nseindia.com/api/corporate-announcements?index=equities&from_date={start-date-DD-MM-YYYY}&to_date={end-date-DD-MM-YYYY}&symbol={symbol}
The endpoint supports the complete requested interval in one navigation. Repeat it once per requested symbol; there is no observed multi-symbol parameter. Use goto with waitUntil: domcontentloaded, then run the extractor below in the same browser session. If authentication, CAPTCHA, or an access-denied response appears, stop and report the blocked source rather than attempting circumvention.
2. Run this page-local extractor on the loaded JSON response, replacing the two ISO date placeholders:
(() => {
const start = "{start-date-YYYY-MM-DD}";
const end = "{end-date-YYYY-MM-DD}";
const clean = v => String(v ?? "").replace(/\s+/g, " ").trim();
const months = {jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12};
const iso = (y,m,d) => `${String(y).padStart(4,"0")}-${String(m).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
const parseDate = value => {
const s = clean(value);
let m = s.match(/(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})/);
if (m) return iso(+m[1], +m[2], +m[3]);
m = s.match(/(\d{1,2})[-\/\s]([A-Za-z]{3,9})[-\/\s](\d{4})/);
if (m && months[m[2].slice(0,3).toLowerCase()]) return iso(+m[3], months[m[2].slice(0,3).toLowerCase()], +m[1]);
m = s.match(/(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})/);
return m ? iso(+m[3], +m[2], +m[1]) : null;
};
let payload;
try { payload = JSON.parse(document.body?.innerText || "null"); }
catch { return {status:"malformed", url:location.href, bodyPreview:clean(document.body?.innerText).slice(0,500)}; }
const records = Array.isArray(payload) ? payload : (payload?.data || payload?.Data || payload?.Table || payload?.table || payload?.results || payload?.Result);
if (!Array.isArray(records)) return {status:"unexpected_shape", url:location.href, payloadKeys:Object.keys(payload || {})};
const dateNames = /date|dt|time/i;
const attachmentNames = /attach|file|pdf|document|url|link/i;
const findDate = record => {
for (const [key,value] of Object.entries(record || {})) if (dateNames.test(key)) { const d=parseDate(value); if (d) return d; }
for (const value of Object.values(record || {})) { const d=parseDate(value); if (d) return d; }
return null;
};
const seen = new Set();
let hasMalformedRecord = false;
const items = records.map(record => {
if (!record || typeof record !== "object") { hasMalformedRecord = true; return null; }
const date = findDate(record);
if (!date || date < start || date > end) return null;
const links = Object.entries(record).filter(([key,value]) => attachmentNames.test(key) && value != null && clean(value)).map(([key,value]) => ({key,text:clean(value),href:/^https?:\/\//i.test(clean(value)) ? clean(value) : null}));
const text = Object.values(record).filter(v => v != null).map(clean).filter(Boolean).join(" ");
const id = record.id ?? record.ID ?? record.announcementId ?? record.ANN_ID ?? record.newsId ?? record.NEWS_ID ?? `${date}|${text}`;
const key = String(id);
if (seen.has(key)) return null;
seen.add(key);
return {date,record,links,text};
}).filter(Boolean).sort((a,b) => a.date.localeCompare(b.date));
return {status: hasMalformedRecord ? "partial" : "ok", url:location.href, requestedRange:{start,end}, items};
})()- Return the extracted rows chronologically for each symbol, preserving the original API record, announcement text, attachment values, and exact endpoint URL. If no rows are returned, report that the symbol had no matching announcements only when the response was valid and the requested interval was applied; distinguish malformed, blocked, or unexpected responses.
- If the direct API response is unavailable or the caller needs the rendered NSE filings view, use the fallback page route:
https://www.nseindia.com/companies-listing/corporate-filings-announcements?symbol={symbol}&tabIndex=equity
Wait for the table to populate, then run the fallback table extractor below. Stop rather than bypassing any CAPTCHA or authentication wall.
Fallback rendered-page extractor:
(() => {
const start = "{start-date-YYYY-MM-DD}";
const end = "{end-date-YYYY-MM-DD}";
const month = {jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12};
const iso = (y,m,d) => `${String(y).padStart(4,"0")}-${String(m).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
const parseDate = value => {
const s = String(value ?? "").replace(/\s+/g," ").trim();
let m = s.match(/(\d{4})[-\/]([01]?\d)[-\/]([0-3]?\d)/);
if (m) return iso(+m[1], +m[2], +m[3]);
m = s.match(/([0-3]?\d)[-\/\s]([A-Za-z]{3,9})[-\/\s](\d{4})/);
if (m && month[m[2].slice(0,3).toLowerCase()]) return iso(+m[3], month[m[2].slice(0,3).toLowerCase()], +m[1]);
m = s.match(/([0-3]?\d)[-\/]([01]?\d)[-\/](\d{4})/);
return m ? iso(+m[3], +m[2], +m[1]) : null;
};
const seen = new Set();
return [...document.querySelectorAll("table tbody tr, [role='row']")].map(row => {
const cells = [...row.querySelectorAll(":scope > th, :scope > td, :scope > [role='cell']")].map(x => x.innerText.replace(/\s+/g," ").trim()).filter(Boolean);
if (!cells.length) return null;
const date = cells.map(parseDate).find(Boolean);
if (!date || date < start || date > end) return null;
const links = [...row.querySelectorAll("a[href]")].map(a => ({text:a.innerText.replace(/\s+/g," ").trim(),href:new URL(a.getAttribute("href"),location.href).href}));
const text = row.innerText.replace(/\s+/g," ").trim();
const key = date + "|" + cells.join("|") + "|" + links.map(x => x.href).join("|");
if (seen.has(key)) return null;
seen.add(key);
return {date,cells,links,text};
}).filter(Boolean).sort((a,b) => a.date.localeCompare(b.date));
})()Optional filters and parameters
index=equitiesselects equity announcements.from_dateandto_dateare inclusive and useDD-MM-YYYYon the direct API endpoint.- Use one
symbol={symbol}parameter per request; preserve the symbol-to-result association. - The rendered fallback requires
tabIndex=equity.
Optional BSE direct API route
For BSE requests, use:
https://api.bseindia.com/BseIndiaAPI/api/AnnSubCategoryGetData/w?pageno={page}&strCat=-1&strPrevDate={start-date-DDMMYYYY}&strScrip={bse-scrip-code}&strSearch=P&strToDate={end-date-DDMMYYYY}&strType=C&subcategory=-1
Navigate to page 1 and run a JSON extractor over the response. If pagination indicates more results, repeat with incremented {page} values and concatenate, deduplicating by announcement identifier or record contents. If only a company name is supplied, resolve its BSE scrip code through an official BSE search or quote result; never guess the opaque numeric code.
Possible Friction Points
- The direct NSE endpoint is a JSON response and is generally shorter and more reliable than opening the rendered filings page.
- Direct API dates use
DD-MM-YYYY; extractor placeholders use ISO dates for unambiguous filtering. - NSE may return different wrapper keys or date-field names; the extractor handles common array and wrapper shapes and scans date-like fields.
- For several companies, issue one direct URL per symbol and keep results grouped by symbol.
- The rendered NSE view requires both
symbol={symbol}andtabIndex=equity; its table is client-rendered. - Do not guess issuer-specific opaque identifiers. NSE accepts the equity symbol directly, while BSE requires the numeric
strScripcode. - BSE attachment values may be filenames rather than absolute URLs; preserve them exactly unless the API supplies a complete URL.
- An empty response or unpopulated rendered table is not confirmation that no filings exist; distinguish unavailable data from a confirmed empty interval.
- The extractors are page-local and make no external requests.