How to Analyze 20-F Filings and ADR Disclosures via API
You run your 10-K section extractor across a global universe and wonder why Taiwan Semiconductor, Alibaba and Shell are missing — then you realize they file 20-F, not 10-K.
In this article, you will learn:
- Why 20-F filings break every tool built for 10-K extraction — silently, not with an error
- The complete 20-F item key dictionary, from Item3D (Risk Factors) to Item16K (Cybersecurity)
- How to map 20-F items to 10-K items for cross-border comparables
- The 3 form types that make up a complete foreign issuer research stack
- How to detect when a foreign private issuer switches to domestic filing status
- And lastly, how to close the 20-F gap in your pipeline — with a free 20-F filings API
Roughly 1,000 foreign companies trade on US exchanges — Taiwan Semiconductor, Alibaba, Shell, Toyota, AstraZeneca — and they are in every global fund, every ADR strategy, and most large-cap screens. They do not file 10-K filings. They file 20-F.
And almost every filings tool, dataset and open-source library in existence was built for the 10-K. Point a section extractor at a 20-F and it returns nothing, because it is looking for Item 1A in a document whose Risk Factors live under Item 3.D. Point a screener at “risk factor changes year over year” and your foreign issuers are quietly absent from the panel — not flagged as missing, just absent. The result is a coverage hole that stays invisible until someone asks why the analysis excludes a fifth of the index.
Why 20-F filings break tools built for 10-K filings
Let me clear up the assumption that causes all the trouble. A 20-F is not a 10-K with a different cover page. It is a structurally different document with its own numbering scheme, mandated by a different SEC form. If you treat it as a dialect of the 10-K, everything downstream inherits that mistake.
| What you want | Where it is in a 10-K | Where it is in a 20-F |
|---|---|---|
| Risk Factors | Item 1A | Item 3.D |
| MD&A | Item 7 | Item 5 — “Operating and Financial Review and Prospects” |
| Financial Statements | Item 8 | Item 17 or Item 18 |
| Controls and Procedures | Item 9A | Item 15 |
| Cybersecurity | Item 1C | Item 16K |
| Executive Compensation | Item 11 | Item 6 (Directors, Senior Management and Employees) |
| Related-Party Transactions | Item 13 | Item 7 (Major Shareholders and Related Party Transactions) |
| Principal Accountant Fees | Item 14 | Item 16C |
Every one of those is a different key. A parser anchored on “Item 7. Management’s Discussion and Analysis” will never find a section titled “Item 5. Operating and Financial Review and Prospects,” because the words do not match and the number is wrong.
It gets worse in the details. 20-F filings use letter sub-items — Item 5A Operating Results, Item 5B Liquidity and Capital Resources, Item 5C Research and Development, Item 5D Trend Information. A 10-K parser has no concept of these. And the financial statements may be IFRS rather than US GAAP, filed under either Item 17 or Item 18 depending on the issuer’s election.
What free and existing options leave you with
Once you understand that the numbering is different, the natural next question is whether existing tools can bridge the gap. The short answer is no — and the reasons are structural, not just a missing feature.
Raw EDGAR gives you the document and nothing else. Every 20-F is free to download — subject to the SEC’s 10-requests-per-second, per-IP ceiling, enforced with a 403 Forbidden and roughly a 10-minute block, and a mandatory User-Agent header naming a real contact. Fine for a handful of names. Painful across a global universe with 20 years of history.
General-purpose parsing tools are built around domestic numbering. Their item dictionaries anchor on Item 1A and Item 7. Point one at a 20-F and it does not error — it simply returns nothing, because the numbers it is looking for are not in the document.
So the work lands back on you. Maintaining a second set of heading heuristics for 20-F, with letter sub-items, IFRS financial statements filed under either Item 17 or Item 18, and heading text that varies by issuer and jurisdiction. All of it silently failing when a filer formats something new.
That is the gap: for a large, liquid, institutionally-held slice of the US market, most pipelines quietly return an empty string.
The 20-F item key dictionary
So maintaining your own heading heuristics is a dead end. The alternative is a pre-built item dictionary that maps every 20-F section to a stable key, the same way 10-K sections map to item1a and item7.
Business Quant’s Section Extraction API covers 20-F alongside 10-K, 10-Q, 8-K, S-1 and F-1 — with the full item set pre-extracted at ingestion and served as clean HTML.
The high-value keys:
| Key | Section |
|---|---|
Item3D | Risk Factors |
Item5 | Operating and Financial Review and Prospects (MD&A) |
Item5A | Operating Results |
Item5B | Liquidity and Capital Resources |
Item5C | Research and Development |
Item5D | Trend Information |
Item16K | Cybersecurity |
Item7 | Major Shareholders and Related Party Transactions |
Item6 | Directors, Senior Management and Employees |
Item8 | Financial Information |
Item15 | Controls and Procedures |
Item16C | Principal Accountant Fees |
Item16J | Insider Trading Policies |
Item17 / Item18 | Financial Statements |
Plus the full remainder: Item3A Selected Financial Data, Item3B Capitalization and Indebtedness, Item4 Information on the Company, Item9 The Offer and Listing, Item10 Additional Information, Item11 Market Risk, Item16A Audit Committee Financial Expert, Item16G Corporate Governance, Item16I Foreign Jurisdictions Disclosure, and more.
Note the casing. 20-F keys are Item3D and Item16K — capital I, no periods, no spaces. 10-K keys are lowercase item1a. The safe rule across every form type: pass back verbatim whatever the /items endpoint returns, and never normalize case.
Mapping 20-F items to 10-K items for cross-border comparables
Remember why any of this matters: the point of extraction is comparison. If you want Taiwan Semiconductor’s risk factors sitting next to Intel’s in the same table, you need one canonical field name with 2 different item keys hiding behind it. That mapping is the whole trick, and it is about 15 lines.
RISK_FACTORS = {"10-K": "item1a", "20-F": "Item3D", "10-Q": "part2_item1a"}
MDNA = {"10-K": "item7", "20-F": "Item5", "10-Q": "part1_item2"}
CYBER = {"10-K": "item1c", "20-F": "Item16K"}
GOVERNANCE = {"10-K": "item10", "20-F": "Item6"}
RELATED_PTY = {"10-K": "item13", "20-F": "Item7"}
AUDIT_FEES = {"10-K": "item14", "20-F": "Item16C"}
CONTROLS = {"10-K": "item9a", "20-F": "Item15"}
That table is the whole cross-border story. With it, a peer-comparison pipeline treats a 20-F filer exactly like a 10-K filer, and your global panel stops having holes in it.
Extracting risk factors and MD&A from a 20-F
The workflow is identical to a domestic filing — only the keys change. Find the most recent 20-F for a ticker, read which items that accession contains, then fetch the sections you need by their 20-F item key. The code below does all 3 steps, and the items[] array on the filing record tells you what is available before you request anything.
# 1. Find the filing
r = requests.get("https://data.businessquant.com/secfilings", params={
"ticker": "TSM", "formtype": "20-F", "page_size": 1, "api_key": API_KEY,
})
filing = r.json()["data"][0]
print(filing["accession"], filing["reportperiod"], filing["items"])
# 2. Fetch the sections, by 20-F key
def section(accession, key):
r = requests.get(
f"https://data.businessquant.com/secfilings/{accession}/content",
params={"item_key": key, "api_key": API_KEY},
)
return BeautifulSoup(r.text, "html.parser").get_text("\n", strip=True)
risk = section(filing["accession"], "Item3D") # Risk Factors
mdna = section(filing["accession"], "Item5") # Operating and Financial Review
cyber = section(filing["accession"], "Item16K") # Cybersecurity
The items[] array on the filing record tells you which sections that particular issuer actually included — before you request them. Foreign private issuers exercise more discretion than domestic filers, so checking first is not optional.
Building a foreign issuer research pipeline
Extracting sections from a single 20-F is the starting point. A complete picture of a foreign private issuer spans 3 form types, and the API handles all 3 from one search:
| Form | What it is | Extractable sections |
|---|---|---|
| 20-F | Annual report | Yes — full item set |
| 6-K | Current report (the FPI equivalent of an 8-K) | No — retrieve as documents |
| F-1 | IPO registration statement | Yes — semantic keys, including DescriptionOfADS |
r = requests.get("https://data.businessquant.com/secfilings", params={
"ticker": "TSM",
"formtype": "20-F,6-K,F-1",
"from_date": "01-01-2020",
"api_key": API_KEY,
})
For the 20-F filings, pull sections by item key. For the 6-K filings, which carry earnings releases, interim results and material announcements, use the Documents and Exhibits API — the substance is almost always in an attached exhibit rather than the body. For F-1 filings, the Section Extraction API provides the cross-border prospectus keys: DescriptionOfADS (the ADS ratio and depositary fees you are actually buying), ServiceOfProcess (whether you can practically sue them), Regulation (including PRC regulatory regimes), and ExchangeRateInfo.
That combination — annual disclosure, interim events, and the original listing document — is a complete foreign-issuer research stack that most tooling cannot assemble at all.
Detecting when a filer switches to domestic status
Here is a signal I have never seen anyone monitor, and I think that is a mistake. A foreign private issuer that loses FPI status must start filing 10-K, 10-Q and 8-K filings instead of 20-F and 6-K. That is a material change in disclosure cadence and obligation — quarterly reporting, current-report requirements, proxy rules, all of it arriving at once. And it usually goes completely unremarked.
The Form Types API returns the distinct form types a company filed within a date window, so you detect the transition by diffing 2 windows. Pull the form set for an older range and a recent one, then compare what appeared and what disappeared. A shift from 20-F and 6-K to 10-K, 10-Q and 8-K is unambiguous.
def forms(ticker, from_date, till_date):
r = requests.get("https://data.businessquant.com/secfilings/formtypes", params={
"ticker": ticker, "from_date": from_date, "till_date": till_date,
"api_key": API_KEY,
})
return set(r.json()["data"]["formtypes"])
old = forms("XYZ", "01-01-2022", "31-12-2022") # {'20-F', '6-K', ...}
new = forms("XYZ", "01-01-2025", "31-12-2025") # {'10-K', '10-Q', '8-K', ...}
print("started filing:", new - old)
print("stopped filing:", old - new)
2 calls. It also tells your extraction pipeline which item-key map to use for which period, which matters when a company’s filing history spans the transition and your panel needs to be continuous across it.
Edge cases in foreign issuer filings
The pipeline above covers the standard path. What follows are the assumptions it makes that will break on specific issuers or filing patterns.
Financial statements are Item 17 or Item 18, not both. Which one depends on the issuer’s election. Check the items[] array rather than assuming.
IFRS, not US GAAP. Many 20-F filers report under IFRS. The narrative sections extract identically, but do not assume line-item comparability with a domestic peer without reconciling the accounting basis.
6-K filings are unstructured by design. Unlike an 8-K, a 6-K has no mandated item taxonomy. It is a wrapper for whatever the issuer is required to disclose at home. The signal is in the exhibits, so route 6-K filings to the documents manifest rather than expecting section keys.
FPI governance exemptions are real. Foreign private issuers may follow home-country practice in place of most US exchange governance standards. Item16G (Corporate Governance) is where they state which exemptions they claim — worth extracting for any governance screen.
Filing deadlines are later. A 20-F is due 4 months after fiscal year end, against 60 to 90 days for a 10-K. Build filing calendars from reportperiod versus filingdate accordingly, and do not assume a missing annual report means a missed deadline.
The short version
Let me leave you with the thing that surprised me most when I started working on this. A fifth of the liquid US-listed universe files 20-F filings — and nearly every extraction tool on the market, free and paid alike, was built for the 10-K. The item numbers do not match. The section titles do not match. And the tooling does not tell you it failed. It quietly returns nothing, and your global universe silently becomes a domestic one.
Business Quant’s Section Extraction API covers 20-F with the full item set — Item3D Risk Factors, Item5 MD&A, Item16K Cybersecurity, Item7 Related Party Transactions, Item17/Item18 Financial Statements — pre-extracted, sub-second, free. Alongside F-1 for the original listing document, and 6-K retrieval through the documents manifest. The SEC Filings API covers 20-F, 6-K, F-1 and 40-F across 400+ form types since 1993, and the Form Types API detects foreign private issuer status changes in 2 calls.
Get the mapping right once and cross-border comparables stop being a special project. They become a lookup table you never think about again.
Frequently asked questions
Where are Risk Factors located in a 20-F filing?
Under Item 3.D, not Item 1A as in a 10-K. The Business Quant Section Extraction API exposes it as the key Item3D — capital I, no period, no separator — and returns it pre-extracted as clean HTML.
Why does my 10-K section parser return nothing for a 20-F filing?
A 20-F uses an entirely different item numbering scheme from a 10-K. A parser anchored on Item 1A or Item 7 does not find those strings in a 20-F because Risk Factors and MD&A are numbered Item 3.D and Item 5 there — the parser does not error, it simply returns nothing.
Where is cybersecurity disclosure in a 20-F versus a 10-K?
Item 16K in a 20-F, and Item 1C in a 10-K. Both cover cybersecurity risk management, strategy and governance, but under different item numbers and different key casing — item1c is lowercase, Item16K is capitalized with no separator.
How do I know when a foreign private issuer switches to filing 10-K instead of 20-F?
Compare the distinct form types a company filed in 2 different date windows using the Form Types API. A shift from 20-F and 6-K to 10-K, 10-Q and 8-K indicates the company lost foreign private issuer status and became a domestic filer.
Is Business Quant's 20-F and F-1 section extraction free to use?
Yes. Both form types are covered with the full item dictionary, pre-extracted at ingestion and served as clean HTML in sub-second lookups, with a free API key.