We parse every annual securities report filed on EDINET — 11,000+ entities, going back roughly a decade. The parser runs in production, processes new filings every 30 minutes, and hasn't needed manual intervention in months.
This is why we built it from scratch.
Why Not Use an Existing Parser?
There are several open-source options for parsing EDINET XBRL in Python. We evaluated all of them.
arelle is the industry standard. It handles every edge case in every jurisdiction's taxonomy — validation, rendering, dimension processing, the full spec. It's also a 200MB install designed for compliance, not for extracting 52 financial fields from Japanese filings at 500ms each. If you need to validate XBRL, use arelle. If you need to power an API, you'll be writing a wrapper around a wrapper.
edinet-tools is actively maintained and supports JP-GAAP and IFRS with downloads and basic parsing. Doesn't parse iXBRL — which is ~60% of recent filings. Doesn't handle consolidated vs non-consolidated context resolution or standard-aware deduplication.
edinet-mcp is the closest to what we built. Extracts 161 fields across three accounting standards, normalizes via suffix-stripping, filters out non-consolidated contexts. Solid work. The non-consolidated filter is binary (always reject) rather than conditional — standalone filers with no consolidated data get zeroed out instead of using their parent-only numbers.
edinet2dataset processes TSV exports from EDINET, not raw XBRL. Designed by Sakana AI for LLM evaluation benchmarks. Good for research batches, wrong interface for a real-time API.
xbrr and edinet_xbrl were early attempts. xbrr hasn't been maintained since 2019. edinet_xbrl's last release was January 2018 — before EDINET's shift to iXBRL. Both require manual context resolution.
The gap across all of them is the same: context priority. A single EDINET filing contains the same financial element in 10+ contexts — consolidated current year, non-consolidated current year, prior year, by segment, by geography. Picking the right one isn't a string match. It requires knowing whether the filer has consolidated data at all, whether the context carries dimension members, and which accounting standard's elements should take priority when multiple map to the same field.
None of these libraries do that. We needed something that does.
870 lines of Python. No machine learning, no LLM, no magic. Just careful XML parsing and a very specific taxonomy.
The Hard Problems
Building an XBRL parser is straightforward. Building one that produces correct data for 11,000+ entities across three accounting standards, two XBRL formats, and a decade of filings — that's where it gets interesting.
The Taxonomy Problem
EDINET uses the FSA's official XBRL taxonomy. It defines thousands of element names across three accounting standards. We need a few hundred of them, mapped to 52 normalized fields. The question is: which ones?
"Revenue" in Japanese financial reporting isn't one concept. It's different terms depending on the accounting standard, the industry, and the section of the report. Banks use a different word than manufacturers. IFRS uses a different word than JP-GAAP. The same company might use different terms in the summary section vs the financial statements section.
We built a pipeline that starts with human-curated Japanese labels — the domain knowledge of what each financial concept is actually called in each context — and automatically matches them against the official FSA taxonomy files. The output is a static mapping loaded once at parser init. Adding a new field is a small config edit and a script re-run.
The domain knowledge is the hard part. The code is trivial.
Two Formats
Every EDINET filing arrives as a ZIP. Inside is either traditional XBRL (pure XML, 10-50MB) or inline XBRL (HTML with embedded tags, 1-5MB). ~60% of recent filings are iXBRL.
Traditional XBRL requires streaming to keep memory flat — you can't hold a 30MB DOM for thousands of filings. Inline XBRL is small enough for full DOM parsing, but has its own traps: scaling attributes that multiply displayed values by powers of ten, sign attributes that override the visible number, and formatting that mixes human-readable text with machine-readable data.
Miss a scaling attribute and every number in your database is off by six orders of magnitude. We've seen this bug in other parsers.
Context Resolution
Every XBRL value has a context that says which period and which entity it belongs to. A typical filing contains both consolidated and non-consolidated financials. Parse the wrong context and you get the parent company instead of the group — a difference that can be tens of trillions of yen for Japan's largest companies.
The context naming is not standardized. Picking the right one requires a priority system that considers period, consolidation scope, and accounting standard simultaneously. Three lines of priority logic that took two weeks of reading raw filings to figure out.
Japanese Numeric Conventions
Japanese financial documents express negative numbers as triangles (△), filled triangles (▲), parentheses, minus signs, full-width minus signs, and em-dashes. Some of these mean "not applicable" depending on the filer. Older filings use Shift-JIS encoding. Every branch in our numeric parser exists because a real company filed a real document that triggered it.
Accounting Standard Transitions
Hundreds of companies have switched between JP-GAAP, IFRS, and US-GAAP over the past decade. Each transition leaves ghost elements from the old standard in subsequent filings. A parser that doesn't track standard transitions will extract values from the wrong standard — and the wrong fiscal period.
The Ingestion Pipeline
New filings appear on EDINET daily. Our pipeline runs every 30 minutes:
- List — query EDINET API for recent dates, filter to reports with XBRL
- Download — fetch the XBRL ZIP, extract the instance document
- Parse — detect format, resolve taxonomy, extract values with correct context
- Persist — upsert company, filing, and financial values to the database
For historical backfill, we parallelize downloads, parses, and writes with bounded concurrency. A multi-year backfill completes in hours, not days.
Per-share values (EPS, BPS, dividends) are stored as integers to avoid floating-point errors. No floats touch the money.
The Eval Suite
870 lines of parser code. Over 4,800 lines of tests. The tests are 5x longer than the parser because XBRL has more ways to be wrong than right.
We run 665 correctness checks against the parser — not smoke tests, but specific scenarios reconstructed from production bugs. Every time a filing breaks something, we add a regression test before we add the fix.
The scenarios cover all three accounting standards, both XBRL formats, consolidated and non-consolidated scoping, standard transitions, company-specific element collisions, and cross-field consistency (e.g., net income and EPS must have the same sign). Each test uses a synthetic XBRL fixture that reproduces the exact structure of the real filing that triggered the bug.
The suite grows monotonically. We haven't deleted a test yet.
What We'd Do Differently
Start with iXBRL. We built the traditional XBRL parser first because older filings use it. But most recent filings are iXBRL, and the DOM parser is simpler to debug.
Invest in the taxonomy pipeline earlier. We spent the first week trying to match element IDs by hand from reading raw filings. The label-matching pipeline would have saved days.
More edge case logging from day one. The warnings that caught real bugs were always the ones we added after debugging a production issue. We should have instrumented more aggressively upfront.
The Result
52 normalized fields. 3 accounting standards. 2 XBRL formats. 870 lines of Python. Company-specific element handling for IFRS filers covers the fields that matter most and that standard taxonomy elements miss. The core fields are fully populated; newer fields are backfilling across historical filings.
Every one of those lines replaces something you'd otherwise have to write yourself — or not write, and get wrong numbers.
import httpx
resp = httpx.get(
"https://api.axiora.dev/v1/companies/7203/financials",
headers={"Authorization": "Bearer ax_live_YOUR_KEY"},
)
data = resp.json()["data"][0]
print(f"Revenue: ¥{data['revenue']:,}")
print(f"Net income: ¥{data['net_income']:,}")
print(f"Total assets: ¥{data['total_assets']:,}")The parser is correct because we checked it against audited filings. The numbers match because we handle the full surface area of EDINET's complexity — formats, encodings, contexts, standards, transitions, and the long tail of company-specific quirks.
If you'd rather not build all of that, the API is free →