Financial data engineering pipelines often focus heavily on headline metrics such as Revenue and Net Income. However, when evaluating the structural cash generation of corporate disclosures, relying solely on top-line and bottom-line GAAP figures presents an incomplete picture. In corporate filings submitted to the U.S. Securities and Exchange Commission (SEC), the Statement of Cash Flows provides the vital reconciliation connecting book accruals to actual cash inflows and outflows.
For developers and data engineers building disclosure parsers, extracting and reconciling these line items programmatically can be tricky. Company reporting structures utilize the US-GAAP XBRL taxonomy, where line items represent time intervals, instant snapshots, or custom extensions. In this guide, we walk through building a Python script to fetch, parse, and reconcile Operating Cash Flow adjustments directly from the public SEC EDGAR API.
The Architecture of SEC EDGAR Facts API
The SEC provides a programmatic API that exposes structured XBRL data directly in JSON format via its EDGAR system. Instead of scraping multi-megabyte HTML tables and wrestling with irregular regex patterns, developers can query the companyfacts endpoint:
https://data.sec.gov/api/xbrl/companyfacts/CIK{cik_10_digits}.json
To interact with this API, the SEC enforces two primary architectural rules:
-
User-Agent Compliance: Requests must provide a descriptive
User-Agentheader containing the user's name and contact email (for example,DataPipeline/1.0 developer@example.com). Anonymous or generic user agents receive immediate HTTP 403 Forbidden errors. - Fair Access Rate Limiting: Applications must not exceed 10 requests per second.
The returned JSON payload organizes reported financial concepts under standard taxonomies—primarily us-gaap and dei (Document and Entity Information). Within each concept, disclosures are structured by currency unit (e.g., USD) containing an array of filing objects.
Why Reconciliations Matter: A Real-World Example
Consider a high-growth tech enterprise filing Form 10-Q. In Qualcomm Incorporated's Form 10-Q for the six months ended March 29, 2026 (CIK 0000804328, Accession Number 0000804328-26-000061), headline GAAP Net Income was reported at $10.375 billion, while Net Cash Provided by Operating Activities was $7.414 billion.
This represents a divergence of -$2.961 billion. Headline earnings were substantially higher than cash collected from operations. Why?
- Share-Based Compensation: A non-cash expense of $1.749 billion added back to cash flow.
- Depreciation & Amortization: A non-cash expense of $806 million added back.
- Cash Tax Adjustments and Working Capital: Substantial timing differences between accrued income tax provisions and actual cash tax disbursements, alongside operational receivable shifts.
Automating the extraction of these reconciling line items allows quantitative pipelines to monitor earning quality automatically.
Implementing the Python Extractor
Below is a complete, self-contained Python implementation utilizing Python's standard library modules (urllib, gzip, and json). It fetches raw company facts, extracts targeted US-GAAP concepts, filters by SEC accession number, and computes the net operating cash flow divergence.
import gzip
import json
import urllib.request
from typing import Any, Dict, Optional
def fetch_company_facts(cik: str, user_agent: str) -> Dict[str, Any]:
"""Fetch structured XBRL company facts from the SEC EDGAR API."""
cik_padded = cik.strip().zfill(10)
url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik_padded}.json"
req = urllib.request.Request(
url,
headers={"User-Agent": user_agent, "Accept-Encoding": "gzip, deflate"},
)
with urllib.request.urlopen(req, timeout=15) as response:
raw_bytes = response.read()
if (
response.info().get("Content-Encoding") == "gzip"
or raw_bytes[:2] == b"\x1f\x8b"
):
raw_bytes = gzip.decompress(raw_bytes)
return json.loads(raw_bytes.decode("utf-8"))
def get_fact_value(
facts_data: Dict[str, Any],
taxonomy: str,
concept: str,
accession_number: str,
start_date: str,
end_date: str,
) -> Optional[float]:
"""Extract a specific concept value by accession number and period."""
concept_entry = (
facts_data.get("facts", {}).get(taxonomy, {}).get(concept, {})
)
units = concept_entry.get("units", {})
for unit_key, records in units.items():
for record in records:
if (
record.get("accn") == accession_number
and record.get("start") == start_date
and record.get("end") == end_date
):
return float(record.get("val", 0.0))
return None
def main() -> None:
# SEC EDGAR CIK for Qualcomm Incorporated
cik = "0000804328"
user_agent = "DataEngineeringTutorial/1.0 (dev@example.com)"
# Form 10-Q period parameters: 6M FY2026 (ended March 29, 2026)
accession = "0000804328-26-000061"
start_date = "2025-09-29"
end_date = "2026-03-29"
print(f"Querying SEC EDGAR facts for CIK {cik}...")
facts = fetch_company_facts(cik, user_agent)
company_name = facts.get("entityName", "Unknown Entity")
print(f"Retrieved disclosures for: {company_name}")
# Extract core cash flow reconciliation concepts
net_income = get_fact_value(
facts, "us-gaap", "NetIncomeLoss", accession, start_date, end_date
)
operating_cf = get_fact_value(
facts,
"us-gaap",
"NetCashProvidedByUsedInOperatingActivities",
accession,
start_date,
end_date,
)
sbc = get_fact_value(
facts,
"us-gaap",
"ShareBasedCompensation",
accession,
start_date,
end_date,
)
depr_amort = get_fact_value(
facts,
"us-gaap",
"DepreciationDepletionAndAmortization",
accession,
start_date,
end_date,
)
print("\n--- Statement of Cash Flows Extraction ---")
print(f"Net Income: ${net_income:,.0f}")
print(f"Operating Cash Flow: ${operating_cf:,.0f}")
print(f"Share-Based Compensation Addback: ${sbc:,.0f}")
print(f"Depreciation & Amortization: ${depr_amort:,.0f}")
if net_income is not None and operating_cf is not None:
divergence = operating_cf - net_income
print(f"\nNet Divergence (OCF - Net Income): ${divergence:,.0f}")
if __name__ == "__main__":
main()
Critical Financial Data Engineering Pitfalls
When operationalizing XBRL extraction scripts into production pipelines, keep these three edge cases in mind:
Duration vs. Instant Concepts: Balance sheet metrics (such as
AssetsorCashAndCashEquivalentsAtCarryingValue) represent a specific point in time and only contain anenddate key. Income statement and cash flow items represent a duration, requiring matching on bothstartandenddates. If your parser ignores thestartdate, it may inadvertently pull a 3-month quarterly figure instead of a 6-month or 9-month year-to-date figure.Accession Number Matching (
accn): Companies frequently revise or reclassify prior-year comparisons in subsequent filings. Filtering strictly by fiscal year (fy) or period (fp) can yield multiple conflicting values across different filing dates. Keying records directly to the accession number (accn) guarantees that all retrieved line items come from the identical filing submission.Accrual vs. Cash Accounting Line Meanings: Accounting reconciliation labels require precise parsing. For example, a cash flow adjustment line labelled “income tax provision less than income tax payments” is an accrual adjustment reflecting the delta between recognized tax expense and actual cash paid. It is not the total cash tax payment itself. Automated systems should label adjustments accurately to avoid distorting downstream financial models.
Conclusion
Automating financial disclosure processing with the SEC EDGAR API removes the fragility of web scraping. By leveraging standard US-GAAP concepts, strictly isolating accession numbers, and respecting duration scopes, data engineers can reliably compute cash flow reconciliations and track corporate cash dynamics at scale.
Top comments (0)