Download XBRL XSD and instance files from a filing
List a filing's XBRL files (schema, linkbases, instance), download the .xsd and _htm.xml, and inspect extension elements and facts with Python.
Most of the time XBRL-to-JSON is what you want: statements already grouped, facts already resolved. Sometimes you need the raw files instead, for example to load a filing into an XBRL processor, archive the original taxonomy, or read the company's own extension elements. Every XBRL filing ships the same set:
| File | What it holds |
|---|---|
*.xsd | The company's extension schema: elements it defined itself, such as its own segment members |
*_htm.xml | The instance document: every fact with its context (period, dimensions) and unit |
*_cal.xml | Calculation linkbase: which facts add up to which totals |
*_def.xml | Definition linkbase: dimension and hypercube relationships |
*_lab.xml | Label linkbase: human readable labels for every element |
*_pre.xml | Presentation linkbase: the order facts appear in each statement |
*-xbrl.zip | All of the above in one archive |
The script
/v1/files lists every document in a filing. /v1/download fetches any of them as filed. This script downloads the schema and the instance of Alphabet's fiscal 2025 10-K, then inspects both with the standard library.
import os
import re
import requests
import xml.etree.ElementTree as ET
BASE = "https://secfeedapi.com"
HEADERS = {"Authorization": f"Bearer {os.environ['SECFEED_KEY']}"}
# Alphabet's 10-K for fiscal 2025
cik, accession = "1652044", "0001652044-26-000018"
files = requests.get(f"{BASE}/v1/files", params={"cik": cik, "accessionNo": accession},
headers=HEADERS, timeout=60).json()
print("XBRL files in the filing:")
xbrl = [f for f in files if re.search(r"\.xsd$|_(cal|def|lab|pre|htm)\.xml$|-xbrl\.zip$", f["name"])]
for f in xbrl:
print(f" {f['name']:<42} {int(f['size'] or 0):>10,} bytes")
os.makedirs("xbrl", exist_ok=True)
for f in xbrl:
if f["name"].endswith((".xsd", "_htm.xml")):
r = requests.get(f"{BASE}/v1/download", params={"url": f["url"]}, headers=HEADERS, timeout=120)
r.raise_for_status()
open(os.path.join("xbrl", f["name"]), "wb").write(r.content)
# the schema: the company's own (extension) elements
xsd = next(f["name"] for f in xbrl if f["name"].endswith(".xsd"))
schema = ET.parse(os.path.join("xbrl", xsd)).getroot()
XS = "{http://www.w3.org/2001/XMLSchema}"
elements = [e.get("name") for e in schema.iter(f"{XS}element")]
print(f"\n{xsd}: {len(elements)} extension elements, for example:")
for name in elements[:6]:
print(" ", name)
# the instance: every fact, by namespace prefix
inst = next(f["name"] for f in xbrl if f["name"].endswith("_htm.xml"))
root = ET.parse(os.path.join("xbrl", inst)).getroot()
ns = {uri: p for p, uri in re.findall(r'xmlns:(\w[\w-]*)="([^"]+)"', open(os.path.join("xbrl", inst)).read(20000))}
facts = [el for el in root if el.get("contextRef")]
by_prefix = {}
for el in facts:
prefix = ns.get(el.tag[1:].split("}")[0], "?")
by_prefix[prefix] = by_prefix.get(prefix, 0) + 1
print(f"\n{inst}: {len(facts):,} facts, {len(root.findall('{http://www.xbrl.org/2003/instance}context'))} contexts")
for p, n in sorted(by_prefix.items(), key=lambda x: -x[1]):
print(f" {p:<8} {n:>6,}")
Output
XBRL files in the filing:
0001652044-26-000018-xbrl.zip 695,448 bytes
goog-20251231.xsd 78,861 bytes
goog-20251231_cal.xml 176,813 bytes
goog-20251231_def.xml 444,460 bytes
goog-20251231_htm.xml 2,880,903 bytes
goog-20251231_lab.xml 1,100,588 bytes
goog-20251231_pre.xml 762,080 bytes
goog-20251231.xsd: 136 extension elements, for example:
CapitalClassCMember
A2.375SeniorNotesDue2028Member
A2.500SeniorNotesDue2029Member
A2.875SeniorNotesDue2031Member
A3.000SeniorNotesDue2033Member
A3.125SeniorNotesDue2034Member
goog-20251231_htm.xml: 1,861 facts, 444 contexts
us-gaap 1,602
goog 143
dei 77
ecd 21
cyd 15
srt 3
The instance holds 1,861 facts. Most use standard us-gaap elements. 143 use Alphabet's own goog elements, defined in the .xsd: mostly members such as share classes and individual bond issues. The ecd facts are executive compensation disclosures and cyd facts are cybersecurity disclosures, both added to the SEC taxonomy in recent years.
Tips
- Inline XBRL. Since 2019 to 2021 (phased by filer size) the primary HTML document is itself inline XBRL. The
_htm.xmlinstance is the SEC's extracted copy, which is easier to parse than the tagged HTML. - Use the zip for archives. One request instead of seven, and it is what XBRL tools expect.
- Older filings. Filings before inline XBRL name the instance
<ticker>-<date>.xmlwithout_htm. Match on the extension and the absence of_cal,_def,_laband_pre. - Fair access. If you download many filings, keep your request rate moderate and cache files: filings never change once accepted.
Next steps
- Download filings and exhibits for the documents themselves.
- Extract segment revenue from a 10-K using XBRL dimensions.
- Filing Download API and XBRL-to-JSON Converter.
- API reference.