If you ask a language model "which free financial data APIs exist?", you get the same list every time: SEC EDGAR, FRED, a couple of vendor free tiers, and yfinance. It is not wrong. It is just very American, and it stops at "here is a URL". This post is about what happens after the URL — what we learned building a small data service that pulls Korea, the United States, Japan and Taiwan from their regulators every night, normalises the result into one schema, and hands it to Claude, ChatGPT or any HTTP client.
The service is FinBridge. It has a free tier and we run it as a business, so read the last section with that in mind. Everything before it is just what we found.
1. The free, official sources actually exist — one per country
The good news first. Every one of the four markets publishes its filings and financial statements for free, from the regulator or exchange itself, and lets you redistribute the data. That last part matters: most vendor free tiers are for personal use only, so you cannot build anything on them that serves other people.
| Market | Source | Key | Limits we live with | Licence and the trap |
|---|---|---|---|---|
| Korea | OpenDART (Financial Supervisory Service) | Free key | 20,000 calls/day | Statements, filings, insider trades. Daily prices are not here — they come from data.go.kr (Financial Services Commission), published the next business day after 13:00 KST, so you are always one session behind. |
| United States | SEC EDGAR | None, but a User-Agent with contact details is mandatory |
10 requests/second | Public domain. The bulk XBRL "financial statement data sets" (DERA) are the only place you get as-filed numbers — see §3. |
| Japan | EDINET (Financial Services Agency) | Free key | Generous | PDL 1.0 — attribution and a note that you processed the data are licence conditions. The archive is a 10-year rolling window: documents fall off the far end, so whatever you fetched is yours to keep, not theirs to re-serve. No prices at all. |
| Taiwan | TWSE OpenAPI / TPEx OpenAPI | None | The exchange's own site tolerates about 0.15 requests/second before it starts serving challenge pages | Open Government Data Licence v1 — attribution required. 143 endpoints on TWSE alone, and none of them is business-segment data. |
| Macro | FRED | Free key | 120 calls/minute | Attribution in every response is a term of use. Some series (S&P 500, NASDAQ Composite) are third-party copyright even inside FRED — do not redistribute those. |
Two absences shaped the whole design. Japan has no redistributable daily prices: EDINET publishes documents, and JPX quote data may not be re-served. Europe (via ESEF, the single electronic reporting format) is the same — statements yes, prices no. So the honest product description is "filings and statements everywhere; prices where the licence allows", and we say exactly that on every relevant page.
We keep the per-market details, including what you cannot get free and why, in a set of guides: Korea · US · Japan · Taiwan, with a one-table round-up that also lists the vendor free tiers fairly.
2. "Adjusted prices" means something different in every market
The US teaches you that split adjustment comes from a corporate-actions feed. Korea has no such feed you may redistribute, so we tried the obvious thing — detect price jumps — and it was wrong in both directions: real 50% rallies looked like reverse splits, and a 1:2 split with a same-day move did not look like anything.
What worked was measuring the split from the listed share count (lstgStCnt in the daily file) and using our own price jumps only as candidates. Of the candidates, 359 turned out to be real corporate actions and 602 were genuine moves that we would have "corrected" away. The share count is the ground truth; the price is the symptom.
The US had the mirror problem. Our first price source under-reported volume by a factor of 20 to 50 (a "mini" dataset that only counts some venues). Closes were right, so nothing looked broken until every liquidity screen quietly excluded Apple. Lesson: verify volume against a known name before you trust a feed, and when you fix it, null out the bad history rather than keep a number that is 3% of reality. Our US volume simply starts on 2024-07-01, and the docs say so.
ETFs are a third case: 1940-Act funds do not file the XBRL split tags that operating companies do, so ETF splits have to be inferred — and the only safe inference is an integer ratio (2:1, 1:4) where the whole day, low to high, sits at the new level. Allow 4:5 in your candidate list and every 20% drawdown becomes a "split".
3. Point-in-time is where free data quietly lies to you
Every free statements source gives you the latest number for a fiscal year. That is correct for a company page and wrong for research: if you backtest a value factor on restated financials, you are trading on numbers nobody had at the time.
For the US, the fix is free but nobody uses it: SEC's DERA quarterly datasets carry every submission with its filing date, so you can rebuild what was knowable on any day. We loaded 141,554 as-filed fiscal years for 5,092 companies (FY2007–2026), keyed by submission, and our factor layer now reads those first. The price was a streaming ZIP reader — num.txt is 600 MB per quarter — and a rule that one submission reporting three fiscal years yields three rows, each stamped with the date it became public.
Korea does not have an equivalent archive, so we keep every DART correction as a new version instead of overwriting, and we tell the model when a number is the restated one. We also learned to record every attempt to re-fetch a corrected filing: Korean "text corrections" do not change the receipt number, and a naive loop refetches them forever.
4. Segments, LEIs and the axis that double-counts
Business-segment revenue was the feature people asked for most, and it comes from three different places: SEC's DERA (the companyfacts API strips dimensional facts, so segments simply are not there), EDINET's XBRL CSVs for Japan, and — for Taiwan — nowhere. The TWSE and TPEx APIs have monthly revenue and industry-level income statements, not reportable segments; we went through all 368 endpoints to be sure.
Traps that cost us a day each:
- A fact with two axes is a breakdown inside a segment. Sum it with the one-axis facts and you double-count.
- The
Geographicalaxis is a country split, not a segment. Some companies, especially Japanese ones, define their reportable segments regionally anyway, so you cannot filter by axis name alone. - US segment names are end markets, not industries: AMD's "Data Center" is chips. A classifier that takes names literally will file it under real estate.
- In ESEF, the entity key is the LEI, and it lives in the filing's
fxo_id, not in the entities table you would naturally join on. Filers also mislabel currencies — statements in EUR with EPS in DKK — so we cross-check against per-share values before trusting a unit. - Half-year reports sit in the same index as annual ones. Group facts by period end, not by year, or an August balance lands in the same row as a February income statement.
5. One more that had nothing to do with finance
For a day, Node could not reach a European filings host that curl on the same machine reached fine. The machine had no global IPv6 route; Node received AAAA records and tried v6 first, the connection opened and TLS never completed. curl goes through glibc and never saw the v6 address. If you ever meet "curl works, Node hangs", pin outbound to IPv4 before you debug anything else.
Related: US tickers get reused. When a new listing inherits a ticker, symbol-mapped feeds prepend the previous owner's history to the new company. We now trim every US name to its first SEC filing date, and any jump detector requires the two prices to be within five sessions — gaps are gaps, not splits.
6. Serving it: MCP for agents, REST for everyone else
Agents connect over the Model Context Protocol — Claude, ChatGPT (developer mode), Cursor and Claude Code all speak it — so FinBridge is an MCP server first: 37 tools over statements, filings, insider trades, segments, prices, valuation with the five nearest peers attached, screeners and cost-aware backtests. Three habits made the model's answers better than the raw data:
-
Every company carries a
page_url. The model cites a page a person can open, with the filing behind each number. -
Every answer carries
data_as_of— last price session, snapshot date, latest reported period — because "the P/E is 14" without a date is not a fact. - Attribution rides along. TWSE/TPEx and EDINET make it a licence condition; we put the source line in the response, not just in a footer nobody reads.
Not everything is an agent, and the lists people consult for "free financial APIs" list APIs, so the same data is also available as plain GET: https://mcp.gronox.kr/api/v1/companies/{kr|us|jp|tw}/{symbol} plus /financials, /valuation, /peers and /prices, with an OpenAPI 3.1 description at /api/v1/openapi.json. Same key, same quota, same depth as MCP.
curl -H "Authorization: Bearer smcp_..." \
https://mcp.gronox.kr/api/v1/companies/kr/005930/financials
7. The commercial part, stated plainly
The free plan is 200 calls a day, every tool, the last four fiscal years and 130 trading sessions, no card. Paid plans buy history depth, not attempts — because the free plan is the same depth as the ~17,600 public company pages, and a free tier deeper than the public site would be unexplainable. Everything is a nightly snapshot of the primary source; nothing is real-time; Japan and Europe have no prices; and none of it is investment advice.
If you only want the map of free sources, the guides are free with no account and will stay that way — they are the notes we wish we had found when we started.
Written from the project's engineering log, with drafting help from Claude. All figures and dates are from our own logs as of 2026-09-06.
Top comments (2)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.