Free, keyless financial data scraped/fetched through a real browser via CDP — the same data the paid financialdatasets.ai API sells, but sourced directly from the free public origins (SEC EDGAR for statements, Yahoo Finance for prices). Each call opens its own tab and WebSocket session — safe for parallel use.
Commands
findata price <ticker> # live/last price snapshot
findata prices <ticker> [--range 1mo|--start S --end E] [--interval 1d] [--limit N]
findata income <ticker> [--period annual|quarterly] [--limit N] # income statement
findata balance <ticker> [--period annual|quarterly] [--limit N] # balance sheet
findata cashflow <ticker> [--period annual|quarterly] [--limit N] # cash flow statement
findata --json <command> ... # raw JSON instead of pretty table
| Flag | Applies to | Meaning |
|---|---|---|
--json |
all | Emit raw JSON instead of pretty-printed text |
--period annual|quarterly |
statements | Reporting period (default annual) |
--limit N |
statements/prices | Statements: periods to show (default 4, 0 = up to 40). Prices: most-recent N bars (default all) |
--range R |
prices | Yahoo range: 1d 5d 1mo 3mo 6mo 1y 2y 5y 10y ytd max (default 1mo) |
--start/--end YYYY-MM-DD |
prices | Exact date range (alternative to --range; needs node) |
--interval R |
prices | Bar interval: 1m 2m 5m 15m 30m 60m 1d 5d 1wk 1mo (default 1d) |
Examples
findata price AAPL
findata prices TSLA --range 3mo --interval 1wk
findata prices MSFT --start 2025-01-01 --end 2025-06-30 --limit 5
findata income AAPL --period quarterly --limit 4 --json
findata balance NVDA --limit 2
findata cashflow AAPL
# Parallel — each call uses its own tab
findata price AAPL & findata price MSFT & wait
Result shapes
price (snapshot):
{"ticker":"AAPL","name":"Apple Inc.","price":310.05,"previous_close":308.63,
"change":1.42,"change_percent":0.46,"currency":"USD","exchange":"NMS",
"time":"2026-07-06T14:16:41Z"}
previous_close is the prior trading day's close (derived from the second-to-last daily bar), not the start-of-range close — so change is the true day-over-day move. price is the live/last trade (regularMarketPrice).
prices (historical OHLCV):
{"ticker":"AAPL","name":"Apple Inc.","currency":"USD","interval":"1d","prices":[
{"date":"2026-07-02","open":294.12,"high":309.42,"low":293.68,"close":308.63,"volume":75352800,"adj_close":308.63}
]}
income / balance / cashflow (statements):
{"ticker":"AAPL","name":"Apple Inc.","cik":320193,"period":"annual","statement":"income",
"statements":[{
"period_end":"2025-09-27","fiscal_year":2025,"fiscal_period":"FY","form":"10-K",
"filed":"2025-10-31","accession":"0000320193-25-000079",
"filing_url":"https://www.sec.gov/Archives/edgar/data/320193/000032019325000079/0000320193-25-000079-index.htm",
"revenue":416161000000,"cost_of_revenue":220960000000,"gross_profit":195201000000,
"operating_income":133050000000,"net_income":112010000000,
"eps_basic":7.49,"eps_diluted":7.46,"shares_basic":14948500000,"shares_diluted":15004697000
}]}
Each statement object carries period_end, fiscal_year, fiscal_period, form, filed, accession, filing_url for traceability back to the SEC filing, plus the line-item fields. cashflow adds a derived free_cash_flow = operating_cf + capex.
Statement fields
- income:
revenue, cost_of_revenue, gross_profit, operating_expenses, sgna, rnd, operating_income, interest_expense, income_tax, net_income, eps_basic, eps_diluted, shares_basic, shares_diluted, dividends_per_share - balance:
total_assets, current_assets, cash, inventory, investments, total_liabilities, current_liabilities, long_term_debt, total_debt, equity, retained_earnings, shares_outstanding - cashflow:
net_income, operating_cf, capex, investing_cf, financing_cf, dividends_paid, share_repurchases, debt_repayment, free_cash_flow
Each field maps to one or more candidate US-GAAP XBRL concepts (preferred order); the concept whose most recent period is latest wins, so a stale legacy concept (e.g. Revenues after a company switches to RevenueFromContractWithCustomerExcludingAssessedTax) never shadows the current one.
How it works
| Data | Source | CDP technique |
|---|---|---|
| Prices | query1.finance.yahoo.com/v8/finance/chart/<ticker>?interval=&range=&period1=&period2= |
Event-driven fetch: navigate to the JSON URL, wait for the Page.frameNavigated commit (which does fire for application/json, unlike loadEventFired/networkIdle), then a same-origin fetch(window.location.href) returns the raw body without waiting for Chrome's JSON-viewer to render it into the DOM. The async projection IIFE runs with awaitPromise. Yahoo's chart endpoint returns clean OHLCV + regularMarketPrice as JSON — no DOM scraping. Going through the browser sidesteps the crumb/consent walls that block curl. |
| Ticker→CIK | www.sec.gov/files/company_tickers.json |
Same event-driven fetch; parsed into a map cached on globalThis.__secTickerMap for the server's lifetime (one fetch per session). |
| Statements | data.sec.gov/api/xbrl/companyfacts/CIK<padded>.json (SEC EDGAR XBRL) |
Readiness poll (not the fetch path — see below). Navigate to the JSON URL, then poll the in-page projection IIFE every ~80ms until it reports {ready:true}. Top-level navigation bypasses CORS; only the small projection crosses CDP, so the multi-MB companyfacts object stays in the page. |
Why two capture strategies. JSON navigations fire neither Page.loadEventFired nor networkIdle (verified), so the event-driven wait gsearch/xsearch use doesn't apply directly. Page.frameNavigated does fire on commit, and a same-origin fetch after it returns the raw body the instant it's downloaded — skipping the JSON-viewer render entirely. That's a clear win for small endpoints (prices ~0.3s, ticker map ~0.25s), where the viewer render is pure overhead with little parse to reuse.
For the multi-MB companyfacts, the opposite is true: by the time document.body.innerText is ready, Chrome's JSON viewer has already parsed the 3.7 MB body, so the projection's own JSON.parse is nearly free (~16 ms). A cold fetch+parse costs ~800 ms there, so polling (total ~1.1s) beats the fetch path (~1.5s). The clean way to skip the viewer render there would be Network.getResponseBody at loadingFinished (~0.56s), but that crashes the harness's WebSocket on multi-MB bodies, so polling is kept for companyfacts.
Per call: Target.createTarget(about:blank, background) → Target.attachToTarget → per-call sessionId → navigate + (fetch | poll) → fire-and-forget closeTab in finally. Same pattern as gsearch/xsearch — safe for parallel use, no activeSessionId clobbering.
Typical latency (warm server, cold HTTP cache): price / prices ~0.3s, statements ~1.1–1.4s (the companyfacts download + parse dominates; the first statement call per session adds ~0.25s for the one-time ticker-map fetch). Warm HTTP cache cuts prices to ~0.25s.
Why these sources
- SEC EDGAR for statements: it is the source of truth —
financialdatasets.ai's statements are derived from it (their JSON even carries the same SECaccession_numberandfiling_url). Free, keyless, structured JSON, both annual (10-K) and quarterly (10-Q). No reason to go anywhere else. - Yahoo Finance v8 chart for prices: the only free source that returns OHLCV as structured JSON (not an HTML table) and covers snapshot + history + intraday in one endpoint.
curlis blocked by bot protection on every free price site; the browser bypasses it. (stockanalysis.com403scurland shows a Cloudflare "Just a moment…" wall even to a real browser; Yahoo's quote page is inconsistent due to EU consent redirects — but its JSON chart endpoint works cleanly through the browser.)
Traps
- Quarterly statements are YTD cumulative, not per-quarter. SEC 10-Q facts are reported year-to-date within the fiscal year (a Q3 figure is the 9-month cumulative).
findatareturns as-reported values — it does not difference them into 3-month stubs. This matches the filing; if you need per-quarter, subtract consecutive periods yourself. Balance-sheet items are point-in-time (not cumulative), as expected. - Cash-flow line-item signs are normalized. SEC filers are inconsistent: some report
capex/dividends_paid/share_repurchases/debt_repaymentas positive magnitudes, some as negative. These four are always cash outflows, sofindatanormalizes them to negative. The net subtotals (operating_cf,investing_cf,financing_cf) keep their as-reported directional signs.free_cash_flowis derived asoperating_cf + capex(withcapexnormalized negative), which equalsoperating_cf − |capex|regardless of the filer's convention. period_endduplicate facts. companyfacts often contains the original 10-K fact and a comparative-column restatement filed in a later 10-K (sameend, same value, the later one tagged with aframelikeCY2024).findatadedupes periods byperiod_endand prefers the earliest-filed10-Kfact, so each column points back to the original filing, not a later comparative.- Concept coverage varies by filer. A field shows
-/nullif the company reports that line item under a concept not in the candidate list (e.g. Apple reports dividends underPaymentsOfDividendsand repurchases underPaymentsForRepurchaseOfCommonStock). The candidate lists cover the common concepts; esoteric filers may leave some fields null. previous_closeforpriceis the close of the prior trading day (second-to-last daily bar of a 5-day window), sochangeis day-over-day.chartPreviousClosein Yahoo's meta is the close before the range start (~5 days ago), not yesterday — that's why it isn't used.- Yahoo rate limits. Yahoo may return
429/ a non-JSON page under heavy use;findatasurfaces this as aYahoo Finance error … non-JSON response (possibly rate-limited). Back off and retry. Intraday intervals are limited by Yahoo to recent windows (e.g. 1-minute bars only for the last ~7 days). - SEC rate limit / User-Agent. SEC asks for <10 req/s and a descriptive
User-Agent; the browser supplies one automatically. Normal use is 1–2 requests per call (ticker map is cached), well within limits. - Ticker map is cached on
globalThis.__secTickerMapfor the server process lifetime, so recently listed tickers won't appear untilbrowser-harness-js --restart. - Share-class tickers use a hyphen. SEC EDGAR's
company_tickers.jsonand Yahoo's chart endpoint both key share-class symbols with-(BRK-B,BF-B), not..findatauppercases the ticker and normalizes.→-, soBRK.B,brk.b, andBRK-Ball resolve. Without this,BRK.Bfailed withTicker not found in SEC EDGAR. - Two capture paths.
Page.frameNavigated+ same-originfetchfor prices and the ticker map (skips the JSON-viewer render — fast for small bodies); readiness polling for the multi-MB companyfacts, where the viewer's pre-parse makes the projection nearly free (a cold fetch+parse is slower there). See How it works. - No lifecycle events for JSON pages. Chrome emits neither
Page.loadEventFirednornetworkIdleforapplication/jsonnavigations. The fetch path keys offPage.frameNavigated(which does fire on commit); the poll path keys offdocument.body.innerTextparseability, bailing fast on a non-JSONcontent-typeinstead of waiting out the timeout. - No
jqdependency — JSON is parsed in-page and projected to the exact output shape; pretty-printing is done in JS. Date→unix conversion usesnode(already required bybrowser-harness-js).