How to Get Earnings Numbers Before the 10-Q Lands
Earnings hit the wire, the stock moves, and your XBRL pipeline does not see the quarter for another 5 to 40 days because the 10-Q is the late document by design. The number you need is already sitting in an 8-K that filed hours ago.
In this article, you will learn:
- Why the 10-Q is structurally weeks late and how the SEC deadline calendar creates the 8-K earnings before 10-Q gap
- How to detect the earnings 8-K the moment it files and parse the preliminary number
- What you can and cannot trust in a preliminary read versus the final XBRL quarter
- How normalized periods reconcile both filings onto one axis — with a free API
Earnings hit the wire. The stock moves that evening. And if your data pipeline is built on XBRL — the clean, tagged financials in the 10-Q — your model does not see that quarter until the 10-Q is filed, which can be weeks later.
Think about what that actually means for a moment. You are watching a price move on information your own system cannot ingest. I have sat in that meeting, and the instinct is always to go looking for a faster feed. That is the wrong instinct. You are not waiting on a slow feed. You are waiting on the wrong document.
2 documents one quarter in a fixed order
2 filings report the same quarter, and they always arrive in the same order. First the 8-K earnings release, filed under Item 2.02, with the numbers in an HTML press-release exhibit — this is what moves the stock. Then, days to weeks later, the 10-Q, with the full statements and the XBRL tagging that makes them machine-readable.
The distinction runs deeper than timing, and this bit is worth understanding rather than memorising. The Item 2.02 information is generally furnished to the SEC, while the 10-Q is filed — a legal difference that, for your purposes, explains why the earnings release carries no financial-statement XBRL and the 10-Q carries all of it. The release exists to inform the market immediately; the periodic report exists to be the complete, tagged, auditable record. You want the first for speed and the second for truth, and the trick is to use both.
Why the 10-Q is structurally late
The gap is not small and not fixed, and it is worth knowing where it comes from, because it tells you how much lead time you are actually buying. The SEC sets the 10-Q deadline by filer status: a large accelerated or accelerated filer must file within 40 days of quarter-end, and a smaller, non-accelerated filer within 45. The 10-K deadlines are longer — 60, 75 or 90 days by the same tiers.
The earnings 8-K, by contrast, has no such deadline — companies release when their numbers are ready, commonly 2 to 6 weeks after the quarter closes. So the sequence is: quarter ends, the company works up its results and issues the 8-K earnings release, and only later, up against the statutory deadline, files the fully tagged 10-Q. The distance between those 2 events is commonly anywhere from about 5 to 40 days depending on the company’s calendar and how early it chooses to pre-announce. Treat that as a range, not a constant. What is invariant is the ordering, and the ordering is what you build on.
Detect the 8-K the moment it files
So the ordering is reliable and the gap is wide. The practical question is how to catch the early filing as soon as it lands. The SEC Filings API indexes new filings within seconds of acceptance by EDGAR, and each record carries an items array and a minute-precise publishdatetime. An earnings 8-K is the one whose items contain Item2_02 — not Item9_01, which is on almost every 8-K and only means it has attachments. Poll for it and compare against the last accession you saw:
import requests, time
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://data.businessquant.com"
def latest_earnings_8k(ticker):
url = (f"{BASE_URL}/secfilings"
f"?ticker={ticker}&formtype=8-K&page_size=25&api_key={API_KEY}")
for filing in requests.get(url).json()["data"]: # newest first
if "Item2_02" in filing.get("items", []):
return filing
return None
def watch_for_earnings(tickers, interval=120):
seen = {}
while True:
for ticker in tickers:
f = latest_earnings_8k(ticker)
if f and f["accession"] != seen.get(ticker):
seen[ticker] = f["accession"]
yield ticker, f # a fresh earnings 8-K
time.sleep(interval)
for ticker, f in watch_for_earnings(["AAPL", "MSFT", "NVDA"]):
print("earnings 8-K:", ticker, f["publishdatetime"], f["accession"])
Because the filing is indexed within seconds of acceptance, a short interval during earnings season catches it promptly. Use publishdatetime, not the fiscal-period date, as your timestamp for “when the market knew” — the fiscal-period date is weeks earlier, and treating it as the moment of knowledge is a look-ahead-bias bug that will flatter any backtest.
Read the early number from the Results of Operations section
Once you have the accession, the next step is pulling the actual number out of it. The Section Extraction API returns the Results of Operations section as clean HTML, stripping the boilerplate so you are looking at the table that matters. You parse that table into rows using the same positional parse covered in why earnings press release tables break scrapers — the label column, the current quarter, and the year-ago quarter, in whatever scale the company chose:
def earnings_html(accession):
url = (f"{BASE_URL}/secfilings/{accession}/content"
f"?item_key=Item2_02&api_key={API_KEY}")
return requests.get(url).text
for ticker, f in watch_for_earnings(["AAPL"]):
html = earnings_html(f["accession"])
rows = parse_table(html) # from the press-release-tables guide
print(ticker, "reported at", f["publishdatetime"], "->", rows[:3])
That early read is the company’s reported figure in its own labels and scale — enough to react to, hours or days before the tagged 10-Q exists.
What you can and cannot trust in the preliminary read
Now, speed has a price, and I want to be precise about it so you use this early number correctly rather than over-trusting it. The 8-K figure is preliminary. It is management’s reported result ahead of the full statements, and it can move by the time the 10-Q is filed — a reclassification between line items, a rounding difference between the condensed press-release table and the audited statement, or, occasionally, a genuine correction.
2 more cautions. First, earnings releases lead with non-GAAP figures — adjusted EPS, adjusted operating income — because that is the number management wants discussed. Those will not tie to the GAAP statement in the 10-Q; they are a different quantity, not a discrepancy, so when you reconcile you match GAAP to GAAP and carry the adjusted line separately. Second, in the rare case a company later determines earlier numbers cannot be relied upon, it files an 8-K under Item4_02 — non-reliance on previously issued financials. Watching for that item alongside Item2_02 is how you catch a restatement before it quietly poisons a series. The early read is a signal to act on; the normalized 10-Q is the record you reconcile it against.
Reconcile with the normalized 10-Q
The early read is useful precisely because you know a better one is coming. When the 10-Q lands, the Financial Statements API carries the normalized, final quarter, keyed by normalizedDate and standard slugs. That quarter is the GAAP figure, concept-mapped and period-aligned, so it slots straight into a time series without the label and scale guesswork you did on the press release. Poll for it and align it to the 8-K read you already captured:
def latest_normalized_quarter(ticker):
url = (f"{BASE_URL}/statements"
f"?ticker={ticker}&statement=IS&frequency=Quarter&period=5y&api_key={API_KEY}")
data = requests.get(url).json()
for category in data["data"].values():
for section in category["sections"].values():
if section["metadata"]["slug"] == "revenue" and section["values"]:
v = section["values"][0] # newest first
return v["normalizedDate"], v["reportedValue"]["raw"]
print(latest_normalized_quarter("AAPL")) # populated within ~10 min of the 10-Q
You get the early read from the 8-K when it matters and the authoritative, normalized read from the 10-Q when it exists — the same period, from 2 filings, reconciled on the date. Store both, keyed by period, and you have a record of what was known when and what it settled to.
Watching many names at once
Of course, in practice you are not tracking one ticker through earnings season. You are tracking a book. The SEC Filings API is company-scoped — you poll per name — and that is a feature, not a limitation: request cost is trivial to reason about and tier, and every request is scoped to a company you actually care about, so 100 watched names is 100 cheap, predictable calls rather than a firehose you have to filter. A watch list is just the loop above over a set, with a little bookkeeping so you record the release time and hand each new section to your parser:
WATCH = ["AAPL", "MSFT", "NVDA", "AMZN", "GOOGL"]
def earnings_events(tickers, interval=120):
seen = {}
while True:
for ticker in tickers:
f = latest_earnings_8k(ticker)
if f and f["accession"] != seen.get(ticker):
seen[ticker] = f["accession"]
rows = parse_table(earnings_html(f["accession"]))
yield {"ticker": ticker,
"reported_at": f["publishdatetime"], # when the market knew
"accession": f["accession"],
"rows": rows}
time.sleep(interval)
for event in earnings_events(WATCH):
print(event["ticker"], event["reported_at"], "parsed", len(event["rows"]), "rows")
This is polling and that is the honest word
Everything above — the single-ticker loop, the watch list, the reconciliation check — runs by polling, and I want to be precise about that because this is where vendors get vague and I would rather not. You poll the SEC Filings API for the new 8-K, and you poll the Financial Statements API for the reconciled quarter. Nothing is pushed to you. I do not think that is a shortcoming — it is what hands you the control. You choose the interval, so you choose the trade-off between promptness and request volume; a short interval during earnings season catches the 8-K within a minute or 2 of its appearing, because it is indexed within seconds, and the normalized quarter follows within 10 to 15 minutes of the 10-Q. Off-season you dial the interval way down because filings are sparse. There is a separate real-time websocket for price quotes — prices tick — but filings and statements do not tick, so they are read by polling, and pretending otherwise would only mislead you about how the system behaves.
Edge cases to keep in mind
With the detection loop and the reconciliation step in place, a few details are worth knowing so you do not trip over them in production.
The lead time varies. How far the 8-K runs ahead of the 10-Q depends on the filer’s calendar and how early it pre-announces; treat the gap as a range, not a constant. What is reliable is the ordering: the earnings release comes first.
Completeness follows the release. Some companies attach full statements to the earnings exhibit; others lead with the income statement and headline balance-sheet lines and leave the rest for the 10-Q. Parse what is there, and let the normalized 10-Q fill in the remainder.
Annual works the same way. The 8-K to 10-K relationship mirrors the 8-K to 10-Q one at year end, so an annual pull sees the reported year ahead of the XBRL 10-K by the same logic and the same deadline structure.
Watch Item4_02 for restatements. A later non-reliance 8-K means a previously captured number is no longer good. Routing on that item keeps a revision from silently corrupting the series you built from earlier releases.
Start Building
Here is the whole thing in one breath. Waiting for XBRL means being weeks late on every single quarter — not because anyone is slow, but because the 10-Q is the late document by design. The SEC calendar puts it there.
So: detect the 8-K the moment it files with the SEC Filings API. Parse its Results of Operations section for the early number. Hold that number correctly in your head — preliminary, possibly non-GAAP, not yet the record. Then let the Financial Statements API deliver the reconciled quarter when the 10-Q lands, and reconcile the 2.
Poll for both and you are reading the print rather than chasing it.
- SEC Filings API — detect the earnings 8-K within seconds, flagged by Item 2.02.
- Section Extraction API — the Results of Operations section as clean HTML.
- Financial Statements API — the reconciled, normalized quarter from the 10-Q.
Get an API key, watch a few tickers through earnings season, and catch the number as it prints.
Frequently asked questions
How much earlier is the 8-K earnings release than the 10-Q?
The 8-K earnings release is filed first and the XBRL-tagged 10-Q follows, commonly 5 to 40 days later depending on the company calendar. The gap exists because the SEC sets a 10-Q deadline of 40 or 45 days after quarter-end by filer status, while the earnings release has no deadline and goes out when results are ready. The exact gap varies, but the ordering is reliable: the earnings release always comes before the periodic report.
Can I trust the preliminary 8-K number?
Trust it as a signal, not as the final record. It is management reported results ahead of the full statements and can move when the 10-Q is filed. It also usually leads with non-GAAP figures that will not tie to the GAAP statement. Use it to react, then reconcile against the normalized 10-Q, and watch for a later Item4_02 non-reliance 8-K that would flag a restatement.
Does the Financial Statements API give me the quarter before the 10-Q?
No. It sources the normalized quarter from the 10-Q and publishes it within 10 to 15 minutes of that filing. To get numbers before the 10-Q, detect the 8-K earnings release with the SEC Filings API and parse its Results of Operations section, then reconcile with the normalized 10-Q later.
How do I detect an earnings 8-K in real time?
Poll the SEC Filings API by ticker for form type 8-K and watch for a new accession whose items array contains Item2_02. Filings are indexed within seconds of acceptance, and publishdatetime gives minute-level precision for the release time. Do not route on Item9_01, which appears on nearly every 8-K.
Is this streaming or polling?
Polling. You poll the SEC Filings API for the new 8-K and the Financial Statements API for the reconciled quarter; nothing is pushed. You control the interval, so you control the trade-off between promptness and request volume. Because the 8-K is indexed within seconds, a short interval during earnings season catches it promptly.
Are the Business Quant APIs free to use?
Yes. Sign up for a free API key and the SEC Filings, Section Extraction and Financial Statements endpoints all run as shown in the samples.