sec-cli
Extract structured data from SEC EDGAR filings for LLM consumption.
Status: v1.0 — all 14 phases complete. sec-cli get, sec-cli diff, and the full parser pipeline (iXBRL fact stream, presentation linkbase, section extraction, three-layer diff, SQLite cache) are implemented and tested. See Roadmap.
Demo
# Fetch Apple's latest 10-K Risk Factors as clean Markdown
sec-cli get AAPL --section "Risk Factors" --output md | head -40
# Year-over-year diff of Nvidia's 10-K (2023 → 2024)
sec-cli diff NVDA --from 2023 --to 2024 --output md
What it does
sec-cli is a fast CLI (and importable Go library) that turns SEC EDGAR filings into clean, structured output you can pipe directly into a language model or analysis pipeline — no API key, no paid service, no runtime dependencies.
- Fetches any 10-K, 10-Q, or 8-K by ticker + year with no API key required
- Detects filing format (iXBRL, PartialIXBRL, PlainHTML, PlainText) and refuses non-iXBRL filings cleanly with a v1.1 pointer — never silently parses bad data
- Extracts financial tables using the iXBRL fact stream + presentation linkbase as the authoritative source — the same data FactSet and Bloomberg read, not HTML table walking
- Extracts free-text sections (Risk Factors, MD&A, Business Overview, 8-K items) using distribution-relative heading detection — structure derived from each document's own inline CSS, not fixed font-size thresholds
- Outputs versioned, schema-stable JSON or Markdown with a calibrated confidence signal per table (
schema_version: "1.0.0")
- Diffs two years of the same filing at structural, lexical, or semantic granularity to surface what substantively changed — not every comma rephrasing
v1.0 supports iXBRL-era filings only (2019+ for large accelerated filers, 2021+ for all filers). Pre-iXBRL filings are detected and refused with a clear error and a v1.1 pointer, never parsed badly.
Requirements
- Go 1.22+
SEC_CLI_USER_AGENT environment variable. EDGAR's Fair Access policy requires every request to identify the caller; without it EDGAR returns HTTP 403.
# macOS / Linux
export SEC_CLI_USER_AGENT="Your Name your-email@example.com"
# Windows PowerShell (current session)
$env:SEC_CLI_USER_AGENT = "Your Name your-email@example.com"
# persist for all future terminals:
[Environment]::SetEnvironmentVariable("SEC_CLI_USER_AGENT", "Your Name your-email@example.com", "User")
Build & run
# build binary to ./bin/
go build -o bin/sec-cli ./cmd/sec-cli # bin/sec-cli.exe on Windows
# or run straight from source
go run ./cmd/sec-cli get AAPL
Available make targets:
make build # compile binary to ./bin/
make test # go test ./...
make accuracy # run accuracy harness against internal corpus
make lint # staticcheck + go vet
Commands
get — fetch, parse, and render a filing
sec-cli get AAPL # latest 10-K, JSON
sec-cli get AAPL --year 2023 --output md # 2023 10-K as Markdown
sec-cli get AAPL --section "Risk Factors" # single section only
sec-cli get AAPL --section 1A # by item id
sec-cli get AAPL --type 10-Q --output text # latest 10-Q as plain text
sec-cli get AAPL --accession 0000320193-24-000123 # pin to exact filing/amendment
sec-cli get AAPL --no-cache # bypass local SQLite cache
Flags: --type/-t (default 10-K), --year, --accession, --output/-o (json, md, text), --section/-s, --no-cache.
--section accepts an item ID (1A) or a title substring (risk). On a miss, exits 1 and lists all available sections. --accession overrides --year and handles amendments (10-K/A, 10-Q/A).
Every JSON response carries schema_version: "1.0.0". Every Markdown/text response opens with <!-- sec-cli v0.1.0 schema 1.0.0 -->.
diff — compare two filings year-over-year
sec-cli diff AAPL --from 2022 --to 2024 # structural diff, JSON
sec-cli diff AAPL --from 2022 --to 2024 --output md # Markdown report
sec-cli diff AAPL --from 2022 --to 2024 --layer lexical # word-level diff
sec-cli diff MSFT --from 2021 --to 2023 --section "1A" # Risk Factors only
Flags: --type/-t, --from, --to, --layer (structural, lexical, semantic), --output/-o (json, md).
Three diff layers:
| Layer |
What it shows |
Status |
structural (default) |
Subsection-grain: added / removed / modified / unchanged |
✅ |
lexical |
Word-level diffs within modified paragraphs, annotated [+..+] / [-..-] |
✅ |
semantic |
Embedding-distance ranking of modified paragraphs — surfaces substantive change, filters year-roll noise |
planned v1.0.1 |
Financial tables diff by aligning rows on GAAP concept (us-gaap:Revenues matches whether labeled "Net revenues" or "Net sales") and columns by period. Concept-based alignment is free from the iXBRL fact-stream path.
Both endpoints must be iXBRL filings — diffs that span the iXBRL boundary fail fast on the pre-iXBRL side, before the iXBRL filing is fetched.
sections — list detected item sections
sec-cli sections AAPL
sec-cli sections AAPL --year 2023 --type 10-Q
sec-cli sections MSFT --type 8-K
Lists all detected Item sections with resolved titles. Useful to know what --section accepts before calling get or diff.
8-K items use the dotted sub-numbering scheme (Item 1.01, Item 5.02); 10-K/10-Q items use the standard scheme (Item 1A, Item 7). Both are classified correctly.
sec-cli accuracy internal/accuracy/testdata/corpus
# or via make:
make accuracy
Scores the pipeline against a corpus of hand-verified filings. Reports statement cell accuracy and section coverage per filing, split by confidence bucket. All fixtures are hermetic — no network calls.
Current corpus results (synthetic fixtures):
filing ticker format exp →got stmt-acc sect-cov
----------------------------------------------------------------------------
acme ACME IXBRL medium→medium 100.0% 100.0%
globex GLBX IXBRL high →high 100.0% 100.0%
initech INTH IXBRL medium→medium 100.0% 100.0%
umbrella UMBR PartialIXBRL high →high 100.0% 100.0%
----------------------------------------------------------------------------
overall: statement accuracy 100.0% (32/32), section coverage 100.0% (13/13)
confidence calibration: high 100.0% · medium 100.0%
The current corpus uses synthetic hermetic fixtures. Real-filing corpus expansion (AAPL, MSFT, JPM) is planned for v1.0.1 — those numbers will be the honest headline once that corpus is in.
cache — inspect or clear the local SQLite cache
sec-cli cache stats # show size and entry counts
sec-cli cache clear # delete cached parsed documents
sec-cli cache clear --raw # also delete raw bytes (forces re-fetch from EDGAR)
Cache lives at $XDG_CACHE_HOME/sec-cli/cache.db (or ~/.cache/sec-cli/cache.db). Two-tier design:
| Tier |
Key |
Invalidation |
| Raw |
(cik, accession) |
Never — EDGAR filings are immutable; amendments get new accession numbers |
| Parsed |
(accession, parser_version, schema_version) |
Automatic when either version bumps — raw bytes are reused, no re-fetch |
Low-level commands
These pre-get commands expose individual pipeline stages and are useful for debugging.
sec-cli latest AAPL # accession of latest 10-K
sec-cli detect AAPL --year 2024 # IXBRL
sec-cli detect AAPL --year 2018 # PlainHTML → refused cleanly in v1.0
sec-cli fetch AAPL --year 2024 # first 200 bytes of primary document
sec-cli facts AAPL --year 2024 --concept RevenueFromContract
sec-cli table AAPL --year 2024 --statement income
sec-cli table AAPL --year 2024 --layout --index 15 # narrative MD&A table by position
sec-cli text AAPL --year 2024 --section "Risk Factors"
Tests
The suite is hermetic — recorded fixtures and a fake http.RoundTripper mean no network calls and no SEC_CLI_USER_AGENT needed:
go test ./... # all packages
go test ./internal/diff/... -v # diff + word-level lexical tests
go test ./internal/sections/... -v # section classification (10-K + 8-K items)
go test ./internal/accuracy/... -v # accuracy harness against synthetic corpus
go test ./internal/cache/... -v # cache + schema-version migration
Windows note: the suite uses a fake http.RoundTripper rather than httptest.Server, which hangs on some Windows/Go builds due to a cancelIO defect in the standard library's listener teardown. go test -race requires a C toolchain (cgo); it runs in CI.
Python wrapper
A thin Python wrapper (pip install seccli) drives the binary via subprocess and deserializes canonical JSON into typed dataclasses. The JSON schema is the only contract — the Go and Python views never diverge.
import seccli
doc = seccli.get("AAPL") # latest 10-K → Document
doc.metadata.company # "Apple Inc."
doc.tables[0].rows[0].values # [391035000000, 383285000000, ...]
changes = seccli.diff("AAPL", frm=2022, to=2024)
for s in changes.sections:
print(s.item, s.status) # "1A", "modified"
# search() is planned for v1.1
seccli.search("revenue recognition") # raises SecCliError (not yet implemented)
See python/README.md for full installation and API surface.
Distribution
Go install:
go install github.com/kritidutta01/sec-cli/cmd/sec-cli@latest
Homebrew (macOS / Linux):
brew tap kritidutta01/sec-cli
brew install sec-cli
Cross-compiled binaries for darwin/amd64, darwin/arm64, linux/amd64, and linux/arm64 are built via GoReleaser on GitHub Actions on each tagged release.
Architecture
CLI (cmd/)
└─ edgar client rate-limited (9 req/sec), User-Agent enforced
└─ format router classifies IXBRL / PartialIXBRL / PlainHTML / PlainText / Unknown
└─ iXBRL parser
├─ fact-stream extractor PRIMARY — financial statements
│ fact stream + presentation linkbase → rows × columns × periods
├─ layout extractor FALLBACK — untagged narrative tables
│ 5-phase HTML pipeline → rows × columns (lower accuracy ceiling)
└─ section extractor 10-K/10-Q item patterns + 8-K dotted items
└─ normalized model Document → Metadata, []Section, []Table
└─ cache SQLite two-tier (raw + parsed)
└─ renderer JSON / Markdown / text, schema-stamped
Pre-iXBRL paths (PlainHTML, PlainText) are detected and refused in v1.0. The layout extractor exists for narrative tables within iXBRL filings; whole pre-iXBRL support is v1.1.
Roadmap
| Phase |
Scope |
Status |
| 0 |
Scaffold (module, CI, Makefile) |
✅ Done |
| 1 |
EDGAR HTTP client (User-Agent, rate limit) |
✅ Done |
| 2 |
Submissions metadata & CIK lookup |
✅ Done |
| 3 |
Primary-document fetch |
✅ Done |
| 4 |
Format router (iXBRL / HTML / text detection) |
✅ Done |
| 5 |
iXBRL parser: fact stream & contexts |
✅ Done |
| 6 |
Presentation linkbase & table projection |
✅ Done |
| 7 |
Layout fallback for narrative tables |
✅ Done |
| 8 |
Section extraction: 10-K + 8-K item pattern tables |
✅ Done |
| 9 |
Normalized model + JSON / Markdown / text output |
✅ Done |
| 10 |
SQLite cache (two-tier, schema-version keyed) |
✅ Done |
| 11 |
get command — fetch → parse → render pipeline |
✅ Done |
| 12 |
diff command — structural + lexical layers; semantic stub |
✅ Done |
| 13 |
Accuracy harness + hermetic corpus |
✅ Done |
| 14 |
Python wrapper & Homebrew distribution template |
✅ Done |
v1.0.1 (next): --layer semantic embedding-distance diff; real-filing accuracy corpus (AAPL, MSFT, JPM).
v1.1: Whole plain-HTML filings (2010–2018 era). The layout extractor built for v1.0 narrative tables gets wired to handle whole pre-iXBRL filings.
License
MIT