xbrlkit

Work with XBRL filings above Arelle: fetch a filing, parse
it once into a neutral typed model, and project that model into whichever
portable representation you need — or hand it one of those representations and
get the model back.
EDGAR ───────────┐
filings.xbrl.org ├──▶ Arelle ──▶ XbrlModel ──┬──▶ holon.jsonld (RDF / JSON-LD)
XBRL zip / iXBRL ┘ ▲ ├──▶ TAVI (compiled model)
│ ├──▶ xBRL-JSON (OIM)
holon, TAVI ──────┘ └──▶ property graph (parquet, .lbug)
primary HTML ──▶ xbrlkit.text ──▶ sections (text blocks, Items, tables)
holon, TAVI ──▶ xbrlkit view ──▶ the report, rendered in a browser
any of the above ──▶ xbrlkit serve ──▶ MCP client (18 shaped tools)
Three ways in — the SEC, everyone else through
filings.xbrl.org, and the filing itself: an
XBRL package or archive (.zip), an iXBRL document (.htm), a bare instance
(.xml), a filing directory, or an http(s) URL to any of them. Nothing about
the middle of this requires EDGAR, or a regulator at all — a report that was
never filed with anybody parses like one that was.
Four projections out, and two of those read back, so a report that was never an
SEC filing gets the same treatment. A fifth surface, the filing's text, reads
the primary HTML directly and needs neither Arelle nor the network. And
xbrlkit view puts a filing on screen.
It is also a local MCP server. The package stands alone — a library and a
CLI — but xbrlkit serve holds filings in memory and exposes them to Claude,
ChatGPT or any MCP client through eighteen shaped tools, which makes reading a
filing a conversation instead of a script: ask for a statement, the concepts
behind a phrase, what foots to a subtotal, a segment breakdown, an exhibit, or
a regex across the prose. Nothing is indexed and no database sits behind it —
every answer is read from the filing in memory, on your machine.
What you can ask it →
Arelle stays the parser — nobody should reimplement DTS resolution. What it
does not give you is anything ergonomic to hold: ModelXbrl is a large
mutable object graph tied to a controller you have to close. XbrlModel is the
answer to that — stateless, single-filing, lossless, and the waist every
projection hangs off.
The one architectural rule: everything goes through XbrlModel. A feature
that reaches into Arelle's ModelXbrl directly is bypassing the waist, and
that is the change that turns a kit into a junk drawer.
What's in the box
parse | Arelle in, XbrlModel out | the load, the DTS cache policy, taxonomy packages |
serialize | the four projections | holon, TAVI (+ its gap report), xBRL-JSON, the property graph |
deserialize | the importers | a holon or a TAVI read back into the model, no Arelle |
edgar | the SEC | discovery, download, full-text search, 1994 onward |
filings_org | everyone else | ESEF and the national regimes, by LEI |
text | the filing as prose | inline text blocks, 10-K/10-Q Items, the XML forms |
serve | the local MCP server | eighteen shaped tools over a filing in memory |
model.py is the waist itself, schema/ declares the property graph's tables,
query.py runs SPARQL over a built holon, and view.py is the loopback server
behind xbrlkit view and the view_filing tool.
Install
pip install xbrlkit
Exposes the xbrlkit CLI (build, fetch, query, cache, serve) and the
library. Two optional extras: xbrlkit[lpg] for the property-graph projection
(pyarrow, LadybugDB) and xbrlkit[mcp] for the MCP server.
From a source checkout:
brew install uv just
just install
SEC User-Agent
SEC fair access asks for a User-Agent identifying you with contact info.
EDGAR works out of the box under a default that names the project, and the
first unattributed fetch says so once — SEC rate limits per IP, so the shared
default costs nobody else their budget. Identifying yourself is a courtesy,
and one worth extending. just install already created your .env:
SEC_GOV_USER_AGENT="Your Name your@email.com"
.env is loaded automatically by every command run from a checkout of this
repo — the lookup is relative to the installed code, not your working
directory, so a uvx or pip install never picks one up. There, use
export SEC_GOV_USER_AGENT=…, --user-agent, or an MCP env block (see
Serve to an MCP client). Nothing outside EDGAR
needs it — a local file, a JSON report and filings.xbrl.org all load without.
Usage
xbrlkit build --cik 320193 --accno 0000320193-23-000106
xbrlkit build --cik 320193 --accno 0000320193-23-000106 --format tavi
xbrlkit build --cik 320193 --accno 0000320193-23-000106 --format all
xbrlkit fetch --ticker NVDA
xbrlkit query --in output/0000320193-23-000106.holon.jsonld --element us-gaap:Assets
xbrlkit view NVDA
xbrlkit view ./report.zip
xbrlkit serve ./mmm-20241231.htm
From a source checkout, just wraps the same CLI: just build 320193 0000320193-23-000106 and just fetch NVDA.
from xbrlkit.parse import load_model, to_xbrl_model
from xbrlkit.serialize import to_holon, to_tavi_report
from xbrlkit.deserialize import from_holon_json
model = to_xbrl_model(load_model("mmm-20241231.htm"), filing_meta)
holon = to_holon(model)
tavi, gaps = to_tavi_report(model)
model = from_holon_json(holon)
Serve to an MCP client
Two ways to run it. They differ in which process does the fetching, and so in
where your SEC identity goes.
stdio — the client launches the server. The identity belongs in the
server's own env block:
{
"mcpServers": {
"xbrlkit": {
"command": "uvx",
"args": [
"--from", "xbrlkit[mcp]@latest",
"xbrlkit", "serve", "--transport", "stdio"
],
"env": { "SEC_GOV_USER_AGENT": "Your Name you@example.com" }
}
}
}
HTTP — you start the server, the client only points at a URL. An env
block in the client config would reach nothing here; set it on the command:
pip install "xbrlkit[mcp]"
SEC_GOV_USER_AGENT="Your Name you@example.com" xbrlkit serve
SEC_GOV_USER_AGENT="Your Name you@example.com" \
uvx --from "xbrlkit[mcp]@latest" xbrlkit serve
{
"mcpServers": {
"xbrlkit": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
}
}
or, equivalently:
claude mcp add --transport http xbrlkit http://127.0.0.1:8765/mcp
A .env file is not a channel for either of these. The lookup is relative
to the installed code rather than your working directory, so it resolves only
inside a checkout of this repo — a uvx or pip install never sees one. Use
the environment, the env block, or --user-agent.
Both are optional: EDGAR works unattributed under the default, saying so once.
And filings.xbrl.org, local packages and TAVI/holon JSON need no identity at all.
What you can ask it
Load a filing from the chat — a ticker, an EDGAR cik:accession, a lei: for
ESEF and the national regimes, a local package, or a holon or TAVI by path or
URL. A ticker or cik:accession loads the filing's published holon first when
the RoboSystems CDN has one, falling back to EDGAR. Then:
- Pull a statement as a table. The income statement, balance sheet, cash
flow or equity statement — or any disclosure network — as rows in the filer's
own order and labels, values per period column.
- Find the concept behind a phrase. "Revenue", "operating lease liability"
→ the qnames this filer actually reports, its own extension concepts
included, ranked with fact counts and where each appears. You never have to
guess a US-GAAP name.
- Get values by concept and period. Consolidated totals by default — no
dimensional qualifier, the most precise of duplicate tags — or broken out by
any axis the filing carries: segment, product, debt instrument, acquisition.
- Check whether a subtotal foots. The calculation children with their
weights, the reported total against the sum computed from them, per period,
with the difference.
- Read one disclosure whole. A note's rows with values, the same rows by
its own axes, its calculation arcs footed, and its tagged text beside the
numbers. The
disclosures index finds the right block first, cheaply.
- Search the prose. Regex over the whole primary document — Items, the
notes, the cover, the signatures, tagged or not. A pattern that matches more
than fits in the answer says which sections the rest fall in, busiest first.
- Read the other documents. Exhibits, an 8-K's EX-99.1 earnings release —
where the non-GAAP measures and guidance live, since no XBRL holds them — or
a 13F's holdings table.
- Read the forms with no XBRL at all. A Form 4's transactions and holdings,
a 13F's positions, as rows with the document's header fields beside them.
- Find filings worth reading. EDGAR full-text search across the corpus by
phrase, form, date and filer, where every hit carries the id that loads it.
- Export or render it. Write the filing as holon, TAVI, xBRL-JSON, ClawDog,
a LadybugDB graph, or the parse itself; or open it as a rendered report in the
browser and hand back the link.
No graph and no database sits behind any of it: every answer about a filing is
read from that filing. The one outward call is search_filings, which asks
EDGAR's own full-text index which filings to go and read. Full detail,
including the tool table and the --pure profile, in
serve/.
Where it runs
RoboSystems. The platform's SEC pipeline is built on this package: filings
are parsed with xbrlkit.parse (its own Arelle controller, with
register_sec_transforms and the cache policy from configure_webcache),
projected with to_holon, to_tavi_report and the property-graph tables, the
shared sec graph is declared from xbrlkit.schema, and the full-text index
behind its document search is built from xbrlkit.text.
Filing Ladder. The
Filing Ladder benchmark —
one filing handed to the same language model in every representation — built
its 26-filing corpus of 2024–2025 10-Ks and 10-Qs with this package. Each
projection is a rung of the ladder, so its
published results
are also a measurement of what a model can do with each of these outputs. That
corpus is this package's test bench too: the text sections were checked against
the filing's own text-block facts on all 26 filings, the property graph row for
row against the platform's processor, and the JSON importers by round trip.
View & explore
Built holons and TAVI models render in the xbrlkit viewer — the browser
side of the toolkit, a reader that renders the financial statements and lets
you ask questions of the report with AI:
The viewer reads a holon entirely client-side, so a single holon.jsonld is a
complete, portable, self-describing report. Its chat asks the report raw
questions (jq over a TAVI model, SPARQL over a holon); xbrlkit serve is the
other side of that pair — the same filing behind shaped tools, on your own
machine.
xbrlkit view joins the two. It resolves a filing the way serve does,
serializes it, and hands that one document to the viewer:
xbrlkit view NVDA
xbrlkit view "NVDA 10-Q" --as tavi
xbrlkit view 320193:0000320193-23-000106
xbrlkit view lei:549300E9PC51EN656011
xbrlkit view output/x.holon.jsonld
xbrlkit view NVDA --no-open
Without installing anything:
uvx xbrlkit view NVDA
A browser cannot be handed a local path — file:// is unreachable from an
https page, and a file input cannot be pre-populated — so this serves the
document instead, on an ephemeral loopback port with an unguessable path, and
opens xbrlkit.com/view?url=… pointing at it (earlier releases open
xbrlkit.com/?url=…, which keeps working). http://127.0.0.1 is a
potentially trustworthy origin, so the https page may read it; the CORS header
names the viewer's origin and no other. The document is readable there, by that
origin, until you press Ctrl-C. --viewer points at a different build.
From an MCP client the same thing is the view_filing tool: "load NVDA",
then "show me it".
License
MIT © 2026 RFS LLC — see LICENSE.