What It Takes to Build a 6-K and 8-K HTML Parser

Your pipeline handles XBRL perfectly, so it misses every 8-K earnings release and every foreign 6-K interim entirely. You decide to build a parser for them, and the estimate says a sprint. The estimate is wrong, and one of the 2 filings is already parsed for you.

In this article, you will learn:

  • Why the 6-K is already parsed and the real question is whether to build an 8-K parser
  • What makes a non-XBRL parse harder than XBRL — no tags, no linkbase, no ground truth
  • The hidden costs teams forget to price in: a regression corpus, an oracle, drift monitoring
  • A framework for deciding when to build the 8-K parser versus leaning on the normalized 10-Q — with a free API

So you have decided you need the non-XBRL numbers. Maybe it is the 8-K earnings release for a fresh domestic quarter, maybe it is a foreign issuer’s 6-K interim — either way, XBRL alone is leaving you late and leaving foreign filers out entirely.

The next question is whether to build the parser, and it is an expensive question to get wrong in either direction. Build when you did not need to, and you have signed yourself up for permanent maintenance. Skip when you should have built, and you are blind on data that moves your positions. Let me start by narrowing what you would actually be building, because in my experience most of the assumed scope evaporates the moment you look at it properly.

Know what you do not have to build

The 6-K is already handled. The Financial Statements API lists the 6-K among the 5 filings it parses into normalized statements, so a foreign issuer’s interim results come back by ticker in the standard schema without you writing anything. Pull a quarterly series for a foreign filer like TSMC and the 6-K interims are already there, currency-converted and concept-mapped, alongside the domestic quarters:

import requests

API_KEY  = "YOUR_API_KEY"
BASE_URL = "https://data.businessquant.com"

url = (f"{BASE_URL}/statements"
       f"?ticker=TSM&statement=IS&frequency=Quarter&period=5y&api_key={API_KEY}")
data = requests.get(url).json()
print("currency:", data["metadata"]["currency"])   # interim, sourced from the 6-K

That removes the 6-K from your build entirely — and the 6-K is the harder of the 2, because on top of everything an 8-K throws at you it adds foreign presentation conventions, multiple reporting currencies and, on many filers, a decimal comma where you expected a decimal point. What the API does not source is the 8-K — so if you want structured numbers from an earnings release before the 10-Q, the 8-K earnings parser is the real thing on the table, and it is worth scoping precisely.

Why the 8-K parse is harder than an XBRL parse

An XBRL parser is hard, but the input is tagged and the calculation linkbase lets you check your work: if your subtotals do not add up, the linkbase tells you. An 8-K gives you neither. The financials are in an HTML exhibit meant for a human, so there is no tag saying what a cell means, no calculation linkbase to validate against, and no standard layout — every issuer’s table is different, and the same issuer’s table changes when they redesign the press release. You are reconstructing a data structure from a picture with no key and no way to prove you read it right.

The layers the parser owns and the one you can skip

That difficulty is real, but it does not mean you have to solve every part of it from scratch. A correct 8-K earnings parser is roughly these stages — and the first 2, the ones that usually eat the most time, are handed to you by the SEC suite:

  1. Filing and exhibit discovery. Solved: the SEC Filings API flags the earnings 8-K by Item2_02 in its items array and gives you the accession — no scraping the index page, no routing on Item9_01 by mistake.
  2. Clean input. Solved: the Section Extraction API returns the Results of Operations section as clean HTML, and the Documents and Exhibits API returns the raw EX-99.1 by type, both with SGML and inline-XBRL stripped.
  3. Positional grid parsing. Yours: infer the column-to-period map from colspan headers and spacer columns, and pick the income-statement table out of the 5 in the exhibit.
  4. Number recovery. Yours: strip separators and currency, convert parenthesized negatives without mangling parenthesized words, apply the header units scale while sparing per-share rows, detach footnote superscripts.
  5. Label mapping. Yours: map the filer’s wording onto standard line items so the output is comparable, not just extracted.
  6. Reconciliation. Yours: match the preliminary figure to the later 10-Q statement so the series is consistent.

So the build is smaller than the usual telling — the fetch and clean-up are API calls — but stages 3 through 6 are still a real parser, and the mechanics of stages 3 and 4 are covered in why earnings press release tables break scrapers. The 2 stages that guide skips past are the ones that quietly dominate the long-run cost, and label mapping is the worst of them.

Label mapping is the part everyone underestimates

Now here is the stage that quietly becomes the whole project. Extraction gets you rows of “label, number.” It does not get you comparable data — and comparable data is the entire reason you started. “Net sales,” “Total revenue,” “Net revenues,” “Revenues” and “Total net revenues” are one line under 5 names, and different filers — and the same filer in different quarters — will use different ones. You have to build and maintain a synonym map, decide which lines are subtotals versus components so you do not double-count, and keep the adjusted, non-GAAP lines from contaminating the GAAP series they sit next to. None of this is provable from the document, because there is no tag and no linkbase to tell you a mapping is right. Every new issuer you cover is a new set of labels to reconcile by hand, and every redesign can introduce a new one. This is the stage that never reaches “done.”

Why the maintenance never ends

The reason this is a standing cost, not a one-time build, is the absence of XBRL. With tagged data, a filer changing their presentation is an inconvenience; the tags still say what things are. With HTML, a redesign is a break: column offsets move, labels change, a new footnote convention appears, and your positional logic has to absorb it. Multiply that by every issuer across every quarter and the parser is only ever as correct as its last round of maintenance — and the failures are silent, because there is no linkbase to contradict a wrong number. It does not throw; it emits a plausible-looking mistake into whatever consumes it.

The hidden costs people forget to price in

Here is where estimates go wrong, and I have watched it happen more than once. Teams cost the parser and forget the apparatus around it that keeps the parser honest. Because there is no ground truth anywhere in the document, you have to manufacture your own — and that machinery, not the parsing, is most of the ongoing effort:

  • A regression corpus. You need earnings sections from many issuers across several quarters, including known redesigns, as fixtures — otherwise you cannot tell whether a change fixed one filer and broke 3 others.
  • An oracle. Something to check each parse against. The later normalized 10-Q is the natural one: parse the 8-K, and when the 10-Q lands, confirm the preliminary numbers reconcile. Without an oracle, a wrong parse looks exactly like a right one.
  • Silent-drift monitoring. Because failures do not throw, you need checks that catch them — subtotals that stop adding up, a revenue that jumps 3 orders of magnitude, a quarter that goes missing — and someone watching those checks.
  • Coverage of the long tail. The megacaps are the easy filers. Robustness across the smaller, weirder, less consistent issuers is where the real work lives, and it is the work that never ends.

Add it up and the honest estimate for broad coverage is a multi-month initial build plus a permanent fraction of an engineer to keep it correct — not because anyone wrote bad code, but because you are maintaining a parser against documents that have no contract with you and change on the filer’s schedule, not yours.

A framework for build versus call

With the real scope and the hidden costs on the table, here is how I would sort the decision:

Your situationWhat fits
You need foreign interim financialsCall the Financial Statements API. The 6-K is already parsed and normalized; do not rebuild the hardest parser of the set.
You need one company’s latest 8-K number, oncePull the section with the Section Extraction API and parse that single table. No system, no corpus.
A handful of companies with stable press releases, 8-K early readsA narrow parser is workable if you accept it will break on redesigns and can fix it fast.
Broad or unpredictable 8-K coverage at scaleWeigh it carefully. Per-issuer layout robustness plus the oracle and monitoring is the whole cost, and it never goes to zero.
You need it correct without a dedicated maintainerLean on the normalized 10-Q from the Financial Statements API and treat the 8-K as a best-effort early read.

Build the corpus before the parser

If you do have a real reason to parse a set of 8-Ks in-house, here is the one piece of advice that will pay for itself: assemble a broad test corpus before you write a line of the parser. Pull earnings sections from many issuers and several past quarters, including redesigns, with the Section Extraction API, and treat them as regression fixtures. Because there is no linkbase to validate against, your corpus is your ground truth — and a known-good normalized statement from the later 10-Q makes a useful oracle to check each parse against, so a layout change fails a test instead of a dashboard:

def earnings_section(accession):
    url = (f"{BASE_URL}/secfilings/{accession}/content"
           f"?item_key=Item2_02&api_key={API_KEY}")
    return requests.get(url).text

def earnings_accessions(ticker, n=8):
    url = (f"{BASE_URL}/secfilings"
           f"?ticker={ticker}&formtype=8-K&page_size=40&api_key={API_KEY}")
    hits = [f for f in requests.get(url).json()["data"]
            if "Item2_02" in f.get("items", [])]      # earnings only, not Item9_01
    return [f["accession"] for f in hits[:n]]

corpus = {a: earnings_section(a) for a in earnings_accessions("AAPL")}   # regression fixtures

Build the corpus across 12 issuers rather than one, weight it toward the awkward filers, and keep every redesign you find — those are the cases that will break you later, so they are the ones worth pinning down as tests now.

Start Building

Let me collapse this down to the decision you came here to make. The 6-K is already parsed for you — that is the hardest non-XBRL parser in the set, and it is done. So the real build question was never “6-K and 8-K.” It is only the 8-K earnings parser, and that one is genuinely harder than XBRL, because there are no tags and no ground truth to check yourself against.

The SEC Filings and Section Extraction APIs hand you discovery and clean input, which removes the 2 stages that usually eat the most time. What remains yours is the positional parse, number recovery, label mapping and reconciliation — plus the corpus, the oracle and the drift monitoring that keep all of it honest. That last group is permanent maintenance, not a sprint.

So: build it only for a set you can genuinely babysit. For everything else, lean on the normalized 10-Q and spend the engineer elsewhere.

Get an API key, build a fixture corpus from a few issuers, and scope the parser against what one call already returns.

Frequently asked questions

Do I need to build a 6-K parser?

No. The Financial Statements API lists the 6-K among the 5 filings it parses into normalized statements, so foreign interim results come back by ticker in the standard schema. The 6-K is actually the harder parser of the 2, because it adds foreign presentation conventions, multiple currencies and decimal-comma formatting on top of everything an 8-K throws at you, and it is already handled, which leaves the 8-K earnings release as the real build question.

Why is building an 8-K parser harder than an XBRL parser?

The 8-K carries no XBRL, so there is no tag telling a parser what a cell means and no calculation linkbase to validate against. The financials are human-facing HTML tables that differ by issuer and change when a company redesigns its press release, so you reconstruct a data structure from a picture with no key and no way to prove you read it right.

What is the part of the build teams underestimate most?

Label mapping and the test apparatus around it. Extraction gets you label-and-number rows, but making them comparable means maintaining a synonym map across issuers, separating subtotals from components, and keeping non-GAAP lines out of the GAAP series. Because nothing in the document proves a mapping is right, you also need a regression corpus, an oracle from the later 10-Q, and silent-drift monitoring, none of which the parser code itself includes.

Which parts of the build does the SEC suite handle?

Discovery and clean input. The SEC Filings API flags the earnings 8-K by Item2_02 and gives the accession, and the Section Extraction and Documents APIs return the section or exhibit as clean HTML with SGML and inline-XBRL stripped. The positional parse, number recovery, label mapping and reconciliation are yours.

When should I build an 8-K parser versus lean on the 10-Q?

Parse a single section for a one-off, and a narrow parser can work for a few stable issuers if you can fix breaks fast. For broad or unpredictable coverage without a dedicated maintainer, treat the 8-K as a best-effort early read and rely on the normalized 10-Q from the Financial Statements API.

Are the Business Quant APIs free to use?

Yes. Sign up for a free API key and the Financial Statements, SEC Filings and Section Extraction endpoints all run as shown in the samples.