How to Get Fresh Financial Data Minutes After a Filing
A company files its 10-Q and your pipeline does not reflect the quarter for hours or days, even though your API responds in milliseconds. The bottleneck was never the request. It is the ingestion that turns a filing into a statement.
In this article, you will learn:
- Why a real-time financial statements API is only as current as its ingestion, not its response time
- The 3 filing dates every record carries and which one is the moment of knowledge
- How to detect a filing within seconds and read the normalized statement within minutes
- How to choose a polling cadence that matches the way filings actually arrive — with a free API
“Real-time” is the most abused word in financial data, and I would like to take it back for a moment. For statements it does not mean a websocket. A balance sheet does not tick. What it actually means is the gap between a company filing and that filing being readable as a normalized statement.
Get that latency right and your model reflects the latest quarter within minutes. Get it wrong and you are running yesterday’s numbers no matter how fast your requests are — which is the part that catches people, because fast requests feel like freshness. The whole game is on the ingestion side, not the request side. Once you see that clearly, the rest of the design falls out of it.
The bottleneck is ingestion not the request
Every fundamentals feed is downstream of the same event: a filing publishes on EDGAR. What separates a fresh feed from a stale one is what happens in the minutes after that — 4 stages, and a feed is only as current as its slowest one:
- Detect the filing as soon as it is indexed, and record the right timestamp for it.
- Parse it — XBRL for a 10-K, 10-Q, 20-F or 40-F, and HTML tables for a 6-K that carries no XBRL.
- Normalize it into standardized line items so it lines up with history.
- Serve it so a request returns the new period.
The parse is where feeds diverge, and normalization is where they quietly differ in quality. Understanding each stage is what lets you reason about how fresh any statements feed actually is — including one you might build yourself.
Detect the filing and get the dates right
Detection is the easy part — the SEC Filings API indexes filings within seconds of EDGAR acceptance. The subtlety is not whether you saw the filing. It is which date you attach to it. A filing carries 3 of them, and picking the wrong one is not a style choice, it is a bug that will flatter a backtest right up until it trades:
reportperiod— the fiscal period the numbers describe, or the event date. Weeks before anyone outside the company knew.filingdate— the day EDGAR accepted the filing.publishdatetime— the minute it actually went public, at minute precision.
If you are building anything time-aware — a backtest, an alert, a “what did we know and when” audit trail — the moment of knowledge is publishdatetime. Using reportperiod as the timestamp is the classic look-ahead-bias mistake: it credits your model with a quarter’s numbers weeks before they existed, and a strategy backtested on that date will look brilliant and trade like nonsense. Freshness is meaningless if you stamp it to the wrong clock.
Parse is where feeds diverge
Detection and timestamps are the clean part of the pipeline. The messy part is what happens next: turning the filing into numbers. For a domestic 10-K or 10-Q, and for a 20-F or 40-F, the financials arrive as XBRL — tagged, machine-readable, the case everybody handles. Then there is the 6-K, and the 6-K is where feeds quietly go to die. Foreign issuers report interim results on a 6-K, and a 6-K carries no XBRL at all; its numbers live in human-facing HTML tables that every issuer lays out differently, in the same positional-parsing minefield that defeats scrapers on an 8-K. A feed that only ingests XBRL is not slow on the 6-K — it simply does not have foreign interims, which is why so many fundamentals sets go dark on non-US names between annual reports.
Handling the non-XBRL 6-K is the work that lets foreign interims be current in minutes instead of missing entirely, and it is the reason the 10 to 15 minute window is not limited to domestic XBRL filings.
Normalize so the new period lines up with history
Parsing gets you numbers. Normalization is what makes them comparable — and those are not the same achievement, however similar they look on a status page. A raw filing uses the company’s own labels, its own subtotal structure, its own presentation order — and those change over time and differ across issuers. “Net sales,” “Total revenue,” “Net revenues” and “Revenues” are the same economic line under 4 names. A company that reclassifies a cost between quarters, or restates a prior period, breaks any series keyed to the raw label. Normalization maps every filer’s wording onto a stable set of standard line items — keyed by slug — so the newest period drops onto 20 years of history without a manual join, and so a restatement or a presentation change updates cleanly instead of forking your series. Freshness without normalization is just a fast way to get an inconsistent number.
What fresh looks like on the statements side
That is what the pipeline has to do. Here is what the result looks like when all 4 stages run fast enough to matter. The Financial Statements API runs detect, parse, normalize and serve within 10 to 15 minutes of a periodic filing — the 10-K, 10-Q, 20-F, 40-F or 6-K — publishing on EDGAR. You read the result and the newest period is simply already there, keyed by normalizedDate, lined up with every prior period under the same slug. The function below returns that newest date, which is the simplest way to confirm that a fresh filing has been ingested:
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://data.businessquant.com"
def newest_period(ticker, statement="IS", frequency="Quarter"):
url = (f"{BASE_URL}/statements"
f"?ticker={ticker}&statement={statement}"
f"&frequency={frequency}&period=5y&api_key={API_KEY}")
data = requests.get(url).json()
latest = None
for category in data["data"].values():
for section in category["sections"].values():
if section["values"]:
d = section["values"][0]["normalizedDate"]
latest = max(latest, d) if latest else d
return latest
print("newest period on file:", newest_period("AAPL"))
Detect the filing itself within seconds
When you need the earliest possible signal — an 8-K earnings release, or the moment a 10-Q is accepted, ahead of the 10 to 15 minute normalized-statement window — poll the SEC Filings API filtered to today. One gotcha lives right here: the date parameters are DD-MM-YYYY, not ISO, and getting that format wrong returns an empty result rather than an error:
from datetime import date
def todays_filings(ticker, formtype):
today = date.today().strftime("%d-%m-%Y") # DD-MM-YYYY, not ISO
url = (f"{BASE_URL}/secfilings"
f"?ticker={ticker}&formtype={formtype}"
f"&from_date={today}&till_date={today}&api_key={API_KEY}")
return requests.get(url).json()["data"]
for f in todays_filings("AAPL", "8-K,10-Q"):
print(f["publishdatetime"], f["formtype"], f["accession"])
So the 2 clocks are: the SEC Filings API for the filing event in seconds, and the Financial Statements API for the normalized statement in minutes. Use the first to know something landed, the second to read the structured numbers once the periodic filing is in.
You retrieve both by polling
Freshness on the ingestion side is not a push on the delivery side. Both endpoints are a GET; to notice something new you poll and compare against what you last saw — a new accession for filings, a newer normalizedDate for statements. That is a strength: you control the cadence, request cost is easy to reason about and tier, and every call is scoped to a name you care about. There is a separate real-time quotes websocket for prices, but statements do not tick, so filings and statements are read by polling, and it is worth being precise rather than implying a stream that does not exist:
import time
def poll_new_period(tickers, interval=600):
seen = {}
while True:
for ticker in tickers:
d = newest_period(ticker)
if d and d != seen.get(ticker):
seen[ticker] = d
yield ticker, d # a newer statement period
time.sleep(interval) # you choose the cadence
for ticker, d in poll_new_period(["AAPL", "MSFT"], interval=600):
print(ticker, "advanced to", d)
Choosing a polling cadence
One piece of advice I give everyone here: there is no benefit whatsoever to polling faster than filings actually arrive. Match the interval to the workload instead of defaulting to “as fast as possible,” which is a habit rather than a strategy. Because the endpoint is company-scoped, your total request budget is simply your interval times the number of names, which makes the trade-off easy to size:
| Situation | Sensible interval | Why |
|---|---|---|
| Off-season monitoring | Hourly or daily | Filings are sparse; a tighter loop just spends requests on unchanged data. |
| Earnings-season watch list | 1 to 5 minutes | The 8-K is indexed within seconds, so a short interval catches new prints promptly. |
| One-off lookup | No loop, one GET | You just want the latest period on file; read it and move on. |
The point is that cadence is a dial you own, not a fixed property of the feed. Tighten it around the days a name reports and relax it the rest of the quarter.
Freshness spans every periodic filing type
Because the pipeline parses more than domestic XBRL, the 10 to 15 minute window is not limited to the 10-K and 10-Q. A foreign issuer’s 6-K interim and a Canadian 40-F flow through the same detect-parse-normalize-serve path, so the newest period for a foreign filer is current on the same timescale as a domestic one. One endpoint, one freshness story, tagged or not — which is exactly the coverage that a XBRL-only feed cannot match on non-US names.
Pull raw bytes without the rate limit
Sometimes you want the underlying document itself, not the normalized statement — to archive the exhibit, diff a section, or feed a text pipeline. Every SEC Filings record carries a url_bq bundle alongside url_sec: byte-identical paths mirrored on edgar.businessquant.com, which is not SEC-rate-limited. Fetching raw documents through the Business Quant path means bulk retrieval never spends the shared 10-requests-per-second EDGAR budget that quietly 403s pipelines in production:
def fetch_primary_html(accession, filename):
# BQ-hosted content, inline XBRL stripped, not SEC-rate-limited
url = (f"{BASE_URL}/secfilings/{accession}/content"
f"?filename={filename}&api_key={API_KEY}")
return requests.get(url).text
Edge cases to keep in mind
With detection, normalization, polling and raw retrieval covered, a few details are worth keeping in mind so you do not misread the freshness guarantee.
Freshness is availability, not a push. The 10 to 15 minute figure is how quickly a periodic filing becomes readable as a statement, not a promise to notify you. Your polling interval determines how soon you see it.
The 8-K is a filing event, not a statement source. The SEC Filings API surfaces an 8-K in seconds, but its numbers are parsed by you from the Results of Operations section; the normalized statement follows from the 10-Q. See getting earnings before the 10-Q.
Use normalizedDate as your freshness key. It is the stable per-period stamp to compare against your last-seen value, consistent across statements and frequencies, and it is not the same as any of the 3 filing dates — it identifies the period, not the moment of filing.
Amendments get a new accession. An amended filing arrives under a fresh accession and reports the changed period; reconcile on the period rather than assuming the newest accession is a brand-new quarter.
Start Building
So here is the sentence I would pin above the desk. A statements API is only ever as real-time as its ingestion — and the levers are detection, the non-XBRL parse, normalization, and the timestamp you choose to trust. Nothing on the request side can rescue a slow ingestion pipeline.
Detect the filing event in seconds with the SEC Filings API. Read the normalized statement within 10 to 15 minutes of a periodic filing from the Financial Statements API. Stamp everything to publishdatetime, every time, without exception. And poll both on a cadence you choose rather than one somebody sold you.
- Financial Statements API — normalized statements fresh within minutes, by ticker.
- SEC Filings API — the filing event within seconds of acceptance.
Get an API key, poll for the newest period, and stop running yesterday’s numbers on today’s book.
Frequently asked questions
How fresh is the Financial Statements API after a filing?
Normalized statements are available within 10 to 15 minutes of a periodic filing publishing on EDGAR. That covers the 10-K, 10-Q, 20-F, 40-F and non-XBRL 6-K, so a foreign interim is current on the same timescale as a domestic quarterly report.
Which filing date should I treat as when the market knew?
Use publishdatetime, the minute the filing went public at minute precision. Do not use reportperiod, which is the fiscal period the numbers describe and predates public knowledge by weeks, and is not the same as filingdate, the EDGAR acceptance day. Stamping data to reportperiod is a look-ahead-bias bug in any backtest.
Is the real-time financial statements API a websocket or a stream?
No. Filings and statements are read by polling a GET endpoint and comparing against what you last saw. There is a separate websocket for real-time price quotes, but statements do not tick, so they are retrieved by polling rather than pushed, and you control the interval.
How do I detect a filing faster than 10 to 15 minutes?
Use the SEC Filings API, which indexes filings within seconds of acceptance. Poll it filtered to today, remembering the DD-MM-YYYY date format, to catch new 8-Ks or 10-Qs as they land, then read the normalized statement from the Financial Statements API once the periodic filing is parsed.
How often should I poll?
Match the cadence to filing frequency. Hourly or daily is plenty when filings are sparse; 1 to 5 minutes catches new prints promptly during earnings season. Because the endpoint is company-scoped, your request budget is just the interval times the number of names, and polling faster than filings arrive adds no benefit.
Are the Business Quant APIs free to use?
Yes. Sign up for a free API key and both the SEC Filings and Financial Statements endpoints run as shown, with more than 20 years of history behind the latest period.