Research run: 30 August 2026. The local stocks SQLite database was opened read-only; nothing was written.
CSE has a usable, public, undocumented issuer-report index. The scalable official path is:
GET https://www.cse.lk/api/cntSecurity.
securityId, not the security ID returned by allSecurityCode, to POST https://www.cse.lk/api/getFinancialAnnouncement.
fileText, then download each path from the CSE CDN.
The APIs are used by CSE’s public Financial Reports page. They require neither authentication nor an API key in these tests.
curl -sS https://www.cse.lk/api/cntSecurity
The response is an object with status, statusCode, and content. Each content item has:
{
"securityId": 366,
"name": "CENTRAL FINANCE COMPANY PLC",
"symbol": "CFIN",
"boardId": 0,
"deleted": 0
}
For the three test tickers:
| Local/CSE ticker | Bare CSE symbol | cntSecurity.securityId |
|---|---|---|
CFIN.N0000 |
CFIN |
366 |
DIAL.N0000 |
DIAL |
389 |
CTC.N0000 |
CTC |
460 |
GET https://www.cse.lk/api/allSecurityCode is useful to enumerate tradeable full ticker codes; its items are {id,name,symbol,active}. Its IDs are different: CFIN 447, DIAL 471, CTC 381. Passing those IDs to the financial-report API returned zero records. Do not join the two endpoints on id.
curl -sS -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'companyIds=366&fromDate=2010-01-01&toDate=2026-12-31' \
https://www.cse.lk/api/getFinancialAnnouncement
Parameters are:
companyIds: one cntSecurity.securityId; despite the plural name, a single decimal ID works.
fromDate, toDate: ISO YYYY-MM-DD. They are optional, but explicit broad windows are required for history.
Response shape:
{
"reqFinancialAnnouncemnets": [{
"id": 48232,
"path": "cmt/upload_report_file/366_1749119928347.pdf",
"manualDate": 1748370600000,
"uploadedDate": "05 Jun 2025 04:08:48 PM",
"fileText": "Annual Report 2024/25",
"name": "CENTRAL FINANCE COMPANY PLC",
"symbol": "CFIN",
"logoUrl": "upload_logo/366_1601449552.jpeg",
"authorizedDate": null
}]
}
manualDate is Unix milliseconds, but is not reliable as either publication or fiscal-period date. Examples: DIAL 2018 has it null; CFIN has individual interim records with implausible dates; several reports share the same manual date. uploadedDate is a string, authorizedDate can be null, and fileText is free text. Derive the fiscal year from the PDF, not these fields.
Use this canonical construction; it handles both current and historic response paths:
https://cdn.cse.lk/cmt/ + path with a leading "cmt/" removed
Thus both cmt/upload_report_file/366_1749119928347.pdf and historic upload_report_file/366_1559875540282.pdf become valid CDN URLs. I verified the latter returns 200 application/pdf.
Examples tested:
One 2010-01-01 to 2026-12-31 request per issuer returned the following total filings / titles containing “annual report”:
| Ticker | Total filings | “annual report” title matches | Coverage observed |
|---|---|---|---|
| CFIN | 85 | 19 | 2011/12–2025/26, including errata and an AGM notice falsely matched by title |
| DIAL | 134 | 13 | 2012–2025 except the title pattern misses 2021 |
| CTC | 75 | 14 | 2012–2025 |
DIAL’s 2021 PDF is a 176-page document whose first page says Annual Report 2021, even though the CSE title calls it an “Annual Financial Report”. Conversely, CFIN title matches include an erratum and an annual-report notice, not just reports. Title regex is therefore only candidate generation.
Downloaded PDF checks:
| Pages | Text extraction | |
|---|---|---|
| CFIN 2024/25 | 396 | good |
| DIAL 2024 | 268 | good |
| CTC 2024 | 188 | good |
| DIAL 2021 Annual Financial Report | 176 | good; identifies itself as Annual Report 2021 |
| Issuer | Net assets / equity | Parent-attributable earnings | Paid dividends | Verdict |
|---|---|---|---|---|
| CFIN | Yes: Decade at a Glance, printed pp. 376–379, gives “Funds attributable to equity holders of the parent” for 2016–25. | Yes: same table gives “Attributable to equity holders of the parent” for 2016–25. | Not safely from the decade table alone. It says “Gross dividends paid”, but FY 2025 shows Rs. 1.478bn while the current-year cash-flow statement shows Rs. 0.958bn and the report separately calls the Rs. 3.75 final dividend proposed/to be paid in July 2025. | Strong 10-year equity and owner-profit source; use each cash-flow statement/payment schedule for paid dividends. |
| DIAL | Partly: printed p. 237 has a five-year Group “Shareholders funds” series, 2020–24. | No for a clean five-year owner-attributable series: p. 237 reports Group profit after tax, while the audited statement separately reports profit attributable to owners for only 2024/23. | No: p. 238 has dividend per share, not a paid-cash series. The audited cash-flow statement (p. 148) has current/previous paid amounts. | Useful five-year shortcut for parent equity only; ingest annual audited statements for owner earnings and paid dividends. |
| CTC | Only 2024/23 comparisons in Year in Numbers and audited balance sheet (printed pp. 24 and 129). It is a single-company report, so total equity is the shareholder equity. | Only 2024/23; the EPS note identifies 2024 profit attributable to shareholders as Rs. 29.643bn. | Current/previous audited cash flow (p. 147) gives cash Dividends paid; 2024 is Rs. 26.905bn. It differs from declared/appropriated dividends (Rs. 30.298bn). |
No five- or ten-year summary. Retrieve a report per year. |
Important scope rule: CFIN’s ten-year dividend series is Company; its parent-attributable earnings and parent funds are Group. DIAL’s five-year table is Group, and CTC is Company. Preserve that reported scope rather than treating labels as interchangeable.
Use a small, resumable downloader/manifest rather than scraping search results or guessing filenames.
# resolve issuer identity
curl -sS https://www.cse.lk/api/cntSecurity
# retrieve all CFIN filings in a bounded historic window
curl -sS -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
--data 'companyIds=366&fromDate=2010-01-01&toDate=2026-12-31' \
https://www.cse.lk/api/getFinancialAnnouncement
For every CSE ticker:
.N0000, .X0000, etc.) to cntSecurity.symbol. Keep the returned securityId in a durable manifest.
fileText with a broad annual-report expression; reject errata, notice, cover letter, and interim reports only after inspecting document metadata/text. Accept alternate labels such as DIAL’s “Annual Financial Report”.
id, raw path, title, API timestamps, final URL, checksum, download time, and manually resolved fiscal period/scope.
reqFinancialAnnouncemnets); cache the raw payload and tolerate fields being absent/null.
allSecurityCode.id is not cntSecurity.securityId.
Annual Report title filter loses a real report.