| name | sec-filings |
| type | capability |
| description | Fetch and read U.S. SEC filings (10-K, 10-Q, 8-K, and any EDGAR form) for any public company — free, no API key — and extract the information that only lives inside filings: declared risk factors, MD&A, material events, segment data, insider transactions, and quarterly earnings press releases / shareholder letters (filed as 8-K exhibits, EX-99.1 or EX-99.2 — the authoritative source for non-GAAP KPIs like bookings, DAUs, and ARR that fundamentals APIs don't carry; one command lists the exhibit URLs for N quarters of earnings 8-Ks). Resolve a ticker to its filing entity (CIK), list filings by form, date, and 8-K item code, batch-list exhibits, and — for large filings — index the document and BM25-search it down to the few relevant sections, then extract from those sections with cheap sub-agents. Fetches are User-Agent-compliant by construction, so the common EDGAR 403 (and the WebFetch trap) never happen. Triggers: "get the 10-K for X", "latest 10-Q / annual report for Y", "any recent 8-K for Z", "material disclosures / recent filings for <company>", "what risks does <company> declare", "risk factors / MD&A from the latest 10-K", "what does <company>'s 10-K say about <topic>", "find the section on <topic> in <company>'s filing", "show <company>'s SEC filings", "did <company> file anything this week", "insider transactions for <ticker>", "earnings press release / shareholder letter for X", "quarterly bookings / DAU / KPI history for Y", "8-K exhibit / EX-99.1 for Z", "fetch a sec.gov URL without a 403".
|
| requires | ["analyst-kit-core"] |
Preamble (run first)
PLAN MODE EXCEPTION — ALWAYS RUN: this block only reads state and writes to ~/.analyst-kit/.
_AK="$(cat ~/.analyst-kit/core-path 2>/dev/null)"
if [ ! -x "$_AK/bin/analyst-kit-preamble" ]; then
for d in ~/.claude/skills/analyst-kit-core .claude/skills/analyst-kit-core ~/.codex/skills/analyst-kit-core .codex/skills/analyst-kit-core; do
[ -x "$d/bin/analyst-kit-preamble" ] && _AK="$d" && break
done
[ -x "$_AK/bin/analyst-kit-preamble" ] || _AK="$(find ~/.claude/plugins -maxdepth 6 -type d -name analyst-kit-core 2>/dev/null | head -1)"
[ -n "$_AK" ] && { mkdir -p ~/.analyst-kit; printf '%s' "$_AK" > ~/.analyst-kit/core-path; } || true
fi
[ -x "$_AK/bin/analyst-kit-preamble" ] && "$_AK/bin/analyst-kit-preamble" --skill sec-filings 2>/dev/null || echo "AK_CORE: not found (continue without runtime)"
Read the echoed state and act. Skip ALL bullets below if DEDUP: yes or AK_CORE: not found:
DISABLED: yes → this skill is turned off because a required API key isn't
configured. Do not run it. Tell the user it's off, name the missing key (see
MISSING_KEYS), and offer to enable it — either they give you the key now (store it
with "$_AK/bin/analyst-kit-setup" set-key <KEY> <value>, which re-enables the skill) or they
say "set up analyst-kit" for full setup. Then stop; do not attempt the skill's work.
- First run —
ONBOARDED: no → orient the user once, then run
"$_AK/bin/analyst-kit-setup" finish (this covers the telemetry notice, so skip the
TEL_PROMPTED bullet this turn):
- Data home — tell the user Analyst Kit keeps config, API keys, local usage analytics, and
a learnings log together in one folder (default
~/.analyst-kit); offer to move it with
"$_AK/bin/analyst-kit-setup" home <dir>.
- Telemetry (a notice, not a question) — usage telemetry is on by default: only
skill name, duration, outcome, and version, tagged with a per-machine device id
derived from the hardware/OS, never repo names, paths, tickers, or content; opt
out anytime by asking to turn Analyst Kit telemetry off.
- Identity (name + email — required) — ask for the user's name and email, stating
plainly the email is mandatory to use most features (SEC EDGAR's fair-access policy
requires a real, reachable contact on every request). If they decline because they
don't want the email-dependent skills, discover a fallback yourself (
git config user.email, gh api user, …) and pass it with --fallback in step 5.
- Offer full setup — ask if they'd like to configure API keys for all skills now.
If yes, Read
"$_AK/references/intro.md" and follow it. If no, continue — you'll
ask for a key only when a skill needs one.
- Run
"$_AK/bin/analyst-kit-setup" finish --name "<name>" --email "<email>" (add
--fallback for a discovered email; run plain finish only if no email could be
found at all). If it echoes INVALID_EMAIL, re-ask and run it again.
- User asks to set up Analyst Kit at any time (e.g. "set up analyst-kit", "help me set up Analyst Kit", "configure all skills") → Read
"$_AK/references/intro.md" and follow it end
to end: data home, telemetry, every skill's keys, and enabling/disabling each.
TEL_PROMPTED: no (returning user) → give the telemetry notice once, then run
"$_AK/bin/analyst-kit-setup" ack-telemetry.
- If the user asks to turn telemetry off (now or anytime): before flipping it, make a
sincere case once — telemetry is what tells the maintainers which skills break, which
run slow, and where users get stuck, so keeping it on directly improves their
experience; it never includes their data. Offer
"$_AK/bin/analyst-kit-config" set telemetry anonymous (drops the device id) as a middle
ground. If they still want out, run "$_AK/bin/analyst-kit-config" set telemetry off
immediately and without further argument.
- Before running any script that needs an API key: source both the data home's
.env
and the current directory's .env into the shell so scripts can read stored keys:
set -a; [ -f "$AK_HOME/.env" ] && . "$AK_HOME/.env"; [ -f "./.env" ] && . "./.env"; set +a
(where $AK_HOME is the path printed on the AK_HOME: line above). Full lookup order —
always check these before concluding a key is missing or asking the user:
- current shell environment
$AK_HOME/.env (keys stored by analyst-kit-setup)
.env in the current working directory
Never ask the user for a key that is already present in any of these locations.
MISSING_KEYS not none → for each listed key with KEY_PROMPTED_<KEY>: no: explain
where to get it, ask for the value, and run "$_AK/bin/analyst-kit-setup" set-key <KEY> <value>.
If declined, run "$_AK/bin/analyst-kit-setup" skip-key <KEY> (this disables the skills that
need it) and continue — never block the skill.
UPGRADE: UPGRADE_AVAILABLE <old> <new> → say "Analyst Kit skills is available
(you have ) — update?". If yes: Read "$_AK/references/upgrade.md" and
follow it. If declined: run "$_AK/bin/analyst-kit-update-check" --snooze <new>.
LEARNINGS entries shown → these are past mistakes/preferences for this user;
respect them and do not repeat logged pitfalls.
Then proceed with the skill. At the very end, run the Completion block at the bottom
of this file.
SEC filings — fetch & read from EDGAR
This skill answers four families of request, all backed by SEC EDGAR (the
authoritative U.S. filing system — free, no API key, no account):
- Explicit filing requests — "get the latest 10-K / 10-Q / 8-K for X."
- Recent material disclosures — "did anything material happen at X lately?"
(almost always an 8-K, occasionally a 10-Q/10-K item).
- Information that only exists inside filings — declared risk factors,
MD&A, segment breakdowns, legal proceedings, going-concern language,
insider trading. This data is not in price/fundamentals APIs; you must read
the filing.
- Question-answering over a large filing — "what does the 10-K say about
?" A 10-K is 300+ pages; you cannot read it whole. Use the two-step
parse pipeline (below) to find and read only the sections that matter.
Out of scope: 13F institutional-holdings filings have a dedicated skill
(13f-analysis) — route "what does own", "latest 13F", "fund holdings"
there. This skill is for corporate disclosure forms (10-K/10-Q/8-K, etc.) and
insider Forms 3/4/5.
⛔ The one rule that prevents every "it doesn't work": how to fetch
Never fetch a *.sec.gov or efts.sec.gov URL with the WebFetch tool, and
never with a bare curl. SEC's fair-access policy rejects any request without
a User-Agent that names a contact — you get HTTP 403, every time. WebFetch
cannot set that header, so it always 403s on SEC. This is the #1 reason naive
attempts fail.
Instead, every SEC request goes through the bundled scripts, which always send
the required header:
python edgar.py cik AAPL
python edgar.py filings AAPL 10-K -n 5
python edgar.py doc AAPL 10-K
If you ever must hand-fetch, set the header yourself:
curl -H "User-Agent: your-app you@example.com" "https://data.sec.gov/...".
- Contact string: override the built-in default via
export SEC_EDGAR_UA="your-app your-name you@example.com" (optional — a valid default ships, so no .env key is
required and this skill declares no env:).
- Rate limit: stay under ~10 req/sec. The scripts self-throttle (a short sleep
per call); still self-pace on bulk pulls.
The two-step parse pipeline (for reading large filings)
A 10-K/10-Q is far too large to read into context, and "paginate the whole thing"
buries the answer in boilerplate. Split the job:
Step 1 — index & BM25-search to find the right sections (scripts/parse_filing.py).
It fetches the primary document (UA-compliant), strips the iXBRL/HTML to clean prose
(keeping visible inline facts, dropping the hidden ix:header metadata), chunks it into
section-sized windows tagged with their Item label, and ranks chunks against your
query with pure-Python Okapi BM25 (no embeddings, no network, no LLM — deterministic
and dependency-free). It writes the top sections to small files for step 2.
Why this is the token-efficiency play (measured on Apple's FY2025 10-K): raw iXBRL
HTML is 441,509 tokens; the strip alone → 44,274 tokens (10×); the top-5 BM25
sections for one query → ~6,000 tokens (73× vs raw). The strip wins by deleting
iXBRL plumbing (hidden ix:header facts, scripts, styles), not by rendering tables — so
for numbers, use the XBRL facts API (Scenario 3), not the stripped text. Full converter
decision + landscape: references/sec-xbrl-and-attachments.md.
python parse_filing.py AAPL --form 10-K \
--query "supply chain concentration and single-source suppliers risk" --top 5
python parse_filing.py AAPL --form 10-K --top 5 \
--query "supply chain concentration single-source suppliers" \
--query "manufacturing outsourcing partners dependence" \
--query "component shortages supplier risk"
python parse_filing.py AAPL --accession 0000320193-25-000079 --query "segment revenue"
python parse_filing.py --url https://www.sec.gov/Archives/.../aapl-20250927.htm --query "MD&A liquidity"
python parse_filing.py AAPL --form 10-K
Step 2 — extract the precise answer with a cheap Haiku sub-agent per section.
Each sec_NN.txt is ~6 KB (one section). For each top section, spawn a sub-agent
pinned to the cheapest model, claude-haiku-4-5, scoped to that one section. This is
cheap, parallel, and accurate — and it keeps the 300-page filing out of your context.
In Claude Code, use the Task/Agent tool with model: haiku, one per section file, e.g.:
Spawn (model claude-haiku-4-5): "You are reading one section of
<COMPANY>'s <FORM> (file: sec-output/<acc>/sec_03.txt). Read it, then answer
only from that text: <the user's question>. Quote the exact sentence(s) and
cite the Item number. If the answer is not in this section, reply exactly
NOT IN THIS SECTION."
Run them in parallel, drop the NOT IN THIS SECTION replies, and synthesize the
surviving answers into one response, citing Item + filing date. (Portability note: if
the host has no sub-agent tool — e.g. Codex — just read the top 1–2 sec_NN.txt files
directly. The win is the same: BM25 already narrowed 300 pages to the right 6 KB.)
Why this shape? BM25 is the right tool for "find the section" (keywords, names,
defined terms — exactly how filings are written), and Haiku is the right tool for
"read this one section and answer" (cheapest model, tiny input). Splitting them keeps
the whole thing fast, cheap, and free of any external indexer or embedding service.
Step 0 — Resolve the company to a CIK (every path starts here)
EDGAR is keyed by CIK (Central Index Key), not ticker. edgar.py does this for you
(python edgar.py cik <ticker>, or resolve_cik() in code). Identity edge cases it
already handles — and that bite if you resolve by hand:
- Dotted tickers are stored dash-form.
BRK.B is listed as BRK-B — normalize
. → -. (Verified: BRK.B → Berkshire CIK 0001067983.)
- Multi-class tickers share one CIK.
GOOGL and GOOG both map to 1652044 —
the company files once; the share class is not a separate filer.
- Padding matters.
data.sec.gov 404s on an unpadded CIK in the path; pad to 10
(320193 → CIK0000320193). edgar.py pads automatically; the efts full-text API
accepts unpadded CIKs.
- Not found: a bogus CIK returns HTTP 404.
Scenario 1 — "Get the 10-K / 10-Q / 8-K for X"
python edgar.py filings AAPL 10-K -n 5
python edgar.py doc AAPL 10-K
Under the hood the submissions feed (data.sec.gov/submissions/CIK….json) is the spine:
filings.recent is column-oriented (parallel arrays zipped by index — edgar.py
does the zipping). To read or QA the document, hand its URL to parse_filing.py
(above) rather than fetching it raw — that gets you the search pipeline for free.
Form cheat-sheet: 10-K = annual; 10-Q = quarterly; 8-K = current (material
event); /A suffix = amendment (10-K/A); 20-F/40-F = foreign annual; DEF 14A =
proxy; 4 = insider transaction. Form matching is exact and case/space-sensitive
("10-K" must not match "10-K/A"; "DEF 14A" has a space).
The 1000-filing recent cap: filings.recent holds at most ~1000 filings; older
history lives in overflow files. Pass --history to edgar.py filings (or
include_history=True) to walk them. If you only read recent, you silently miss
anything older — for an active filer that can be < 1 year.
Scenario 2 — "Recent material disclosures / did anything material happen?"
Material events are Form 8-K, usually filed within 4 business days. Each 8-K carries
one or more item codes (in the items column) that classify the event — the fastest
materiality triage without reading prose.
python edgar.py filings AAPL 8-K -n 10
| Item | Meaning | Why it's material |
|---|
| 1.01 / 1.02 | Entry into / termination of a material agreement | M&A, big contracts |
| 2.01 | Completion of acquisition or disposition | Deal closed |
| 2.02 | Results of operations (earnings release) | Quarterly numbers |
| 2.03 / 2.04 | New debt / triggering of debt acceleration | Leverage, covenant breach |
| 3.01 | Delisting / failure to satisfy listing rule | Going-concern signal |
| 4.01 / 4.02 | Auditor change / non-reliance on prior financials | Restatement red flag |
| 5.01 | Change in control | |
| 5.02 | Departure/appointment of directors or officers | CEO/CFO change |
| 7.01 / 8.01 | Reg FD disclosure / Other events | Catch-all |
| 9.01 | Financial statements & exhibits | Almost always present (the attachments) |
Worked example (verified): AAPL's latest 8-K had items = "2.02,9.01" → an
earnings release (2.02) with exhibits (9.01).
Read the 8-K and its attachments. An 8-K is a bundle: the cover-page .htm plus
exhibits. List them, then read the right one:
python edgar.py exhibits AAPL 8-K
python edgar.py attachments AAPL 8-K
python edgar.py cover AAPL 8-K
- On an Item 2.02 earnings 8-K, the numbers live in EX-99.1 or EX-99.2 — some
companies (e.g. Roblox) file the press release as 99.1 and the shareholder letter that
carries the KPI tables as 99.2 (verified). List both and pick by content, never by
number alone. The tables are plain HTML, not XBRL (verified): feed the exhibit URL
to
parse_filing.py --url ….
- ⛔ Never construct an exhibit URL from a guessed filename. Exhibit names are
arbitrary and era-dependent (
ex991-q12026earningsshar.htm, q225shletterex992.htm,
a05-09330pmq123sharehold.htm are all real Roblox exhibits). A 404 on a hand-built
URL means list the folder (exhibits --accession <acc>) — it is never a reason to
fall back to WebFetch (sec.gov 403s it).
python edgar.py cover extracts the cover-page XBRL — but note an 8-K has no
financial XBRL, only dei: cover facts (document type, period, registrant, item flags,
registered securities). Don't expect an income statement from filing.xbrl() on an 8-K.
(When the MCP server is connected, analyze_8k decodes items into described events — see
Optional accelerator below.) Full details: references/sec-xbrl-and-attachments.md.
Scenario 2b — a KPI time series from earnings releases (bookings, DAUs, ARR, …)
Non-GAAP KPIs are not in any fundamentals API and not in XBRL — they only exist in the
quarterly earnings exhibits. Getting "metric X for the last N quarters" is a three-step
batch job, not N rounds of discovery:
1. One call for every exhibit URL (item 2.02 = earnings release):
python edgar.py exhibits RBLX 8-K --items 2.02 -n 17
2. BM25-narrow each exhibit to the sections that carry the metric (each quarterly
release restates the year-ago figure, so every other quarter is enough when budget-bound).
Use 2–3 query phrasings — companies move KPIs between a "highlights" block, a
"reconciliation" table, and a "key metrics" table across quarters, and one phrasing
won't match all eras:
python parse_filing.py --url <exhibit-url> \
--query "<metric> three months ended" \
--query "reconciliation of revenue to <metric>" \
--query "key metrics highlights <metric> growth"
3. Extract with a Haiku sub-agent per section — do NOT read the sections yourself.
Reading sec_NN.txt files in your own context burns premium-model tokens on table
boilerplate; this is exactly Step 2 of the parse pipeline (above). Spawn one sub-agent
(model claude-haiku-4-5) per exhibit, e.g.:
Spawn (model claude-haiku-4-5): "Read the section files sec-output/<slug>/sec_0*.txt
from <COMPANY>'s <period> earnings exhibit. Return ONLY a JSON object
{"period": "YYYY-MM-DD", "<metric>": <absolute USD number>} using the
three-months-ended column (not six/nine-months YTD). Use null if absent — never guess."
Run them in parallel and assemble the array. The YTD-column trap is the #1 wrong-answer
source here: shareholder letters print "Three Months Ended" next to "Six Months Ended" —
the sub-agent prompt must pin the column.
This is the highest-value case, and exactly what the parse pipeline is for:
python parse_filing.py <ticker> --form 10-K --query "<topic>" --query "<rephrasing>"
→ top sections (repeat --query 2–4× with different vocabulary; rank-fused).
- Haiku sub-agent per section → precise, quoted answer (Step 2 above).
Where the topic maps in a 10-K/10-Q (useful as a query hint or to read the section map):
- Risk Factors = 10-K Item 1A (updates in 10-Q Part II Item 1A)
- MD&A = Item 7 (10-K) / Part I Item 2 (10-Q)
- Business = Item 1; Legal Proceedings = Item 3 / Part II Item 1
Full-text search across all filers (you don't know which filing) — efts.sec.gov,
fetched via edgar.http_get (UA-compliant), not WebFetch:
https://efts.sec.gov/LATEST/search-index?q=%22material+weakness%22&forms=8-K&startdt=2024-01-01&enddt=2024-03-31
Returns Elasticsearch-shaped JSON: hits.total.value (capped at 10000 when
relation:"gte"), hits.hits[] (100/page; paginate &from=100), each _source with
display_names, form, file_date, items, ciks; _id = "{accession}:{filename}".
Verified gotchas: the params are startdt/enddt (there is no dateRange=custom
— it returns HTTP 500); full-text search only covers filings 2001-present.
Structured financials (XBRL): for numbers (revenue, segments, EPS) rather than
prose, use SEC's pre-computed facts API — it has already applied scale/sign/units, so you
skip the iXBRL parsing traps:
python edgar.py facts AAPL --grep Revenue
python edgar.py concept AAPL RevenueFromContractWithCustomerExcludingAssessedTax
Trap (verified): the GAAP tag for revenue is company-specific — Apple's is
RevenueFromContractWithCustomerExcludingAssessedTax (113 facts) and SalesRevenueNet
(210), while Revenues has just 11 stale facts. Discover, never hardcode. And the same
concept returns both YTD and quarter facts for one period-end — disambiguate by the
context's period length, not just the end date. Details + the full gotcha catalog (scale,
sign, nil, dimensions): references/sec-xbrl-and-attachments.md.
The big edge cases (the ones that silently produce wrong answers)
- 403 without a
User-Agent — and WebFetch can't send one. Use the scripts. (Verified.)
- The 1000-filing recent cap. Older history needs the overflow files (
--history).
- Form matching is exact and case/space-sensitive (
"10-K" != "10-K/A").
filingDate vs reportDate vs acceptanceDateTime. filingDate = when filed;
reportDate = the period covered (the one you usually want for "Q3 2024");
acceptanceDateTime = exact timestamp (use for "today's filings").
- iXBRL hidden facts. Modern filings embed machine-only facts in a hidden
ix:header/display:none block; parse_filing.py drops these but keeps the visible
inline facts (ix:nonFraction/ix:nonNumeric). Don't index the raw HTML yourself.
- XBRL tag drift. Same concept, different tags across companies/years. Discover, don't assume.
- No automatic rate limiting beyond the scripts' per-call sleep. Self-throttle on bulk.
- Exhibit filenames are arbitrary — never build one from a pattern; list them
(
edgar.py exhibits). And the earnings exhibit may be EX-99.1 or EX-99.2.
Full, verified catalog: references/sec-edgar-direct-http.md.
Optional accelerator — the sec-edgar-mcp MCP server
If (and only if) the sec-edgar-mcp
server is connected, it offers ergonomic conveniences: get_company_info,
get_recent_filings, analyze_8k (decodes item codes into events),
get_filing_sections, get_insider_transactions, XBRL helpers. Use it opportunistically
for those conveniences — but it is not required and not the spine:
- It needs the user to have configured the server with
SEC_EDGAR_USER_AGENT (in the
MCP launch config; the server refuses to start without it). That setup step is exactly
what makes it unreliable in practice.
- The bundled scripts are always available and need no setup, so they are the default
path. Treat MCP as a speedup when present; verify or fall back to the scripts.
Full tool inventory: references/sec-edgar-mcp-tools.md.
References
references/sec-edgar-direct-http.md — the raw HTTP layer the scripts are built on:
every endpoint on data.sec.gov, www.sec.gov/Archives, and efts.sec.gov, with
verified request/response shapes and the exhaustive edge-case catalog.
references/sec-xbrl-and-attachments.md — filing→text conversion decision (with
measured token wins + the converter landscape), the accession-folder/exhibit map, the
verified "8-K = dei cover page only, EX-99.1 = plain HTML" finding, the dei taxonomy, the
facts-API-vs-raw-instance call, and the XBRL parsing gotchas (scale/sign/nil/contexts/dims).
references/sec-edgar-mcp-tools.md — complete inventory of the optional MCP tools
(names, parameters, return shapes, the SEC endpoint each maps to).
Completion (run last)
Audit before you deliver. If this run produced a research deliverable carrying
quantitative or factual claims — a deep dive, thematic/value-chain map, technical
call, company wiki, or financial model — you MUST verify it before presenting it.
If a research-auditor subagent is available (the analyst-kit plugin ships one),
invoke it via the Task tool, handing it the draft and the data artifacts you used;
on a runtime without subagents, run the same checks yourself. Resolve every
CRITICAL finding and disclose any UNVERIFIED ones; never deliver on a FAIL
verdict without fixing it first. Skip this only for pure data-fetch/utility runs
with no analytical claims.
PLAN MODE EXCEPTION — ALWAYS RUN: writes only to ~/.analyst-kit/. Replace OUTCOME with one
of DONE | DONE_WITH_CONCERNS | ERROR | ABORT | NEEDS_CONTEXT.
_AK="$(cat ~/.analyst-kit/core-path 2>/dev/null)"
[ -x "$_AK/bin/analyst-kit-log" ] && "$_AK/bin/analyst-kit-log" end --skill sec-filings --outcome OUTCOME 2>/dev/null || true
If this session surfaced a durable pattern, pitfall, or user preference that would save
5+ minutes next time (not obvious, not a transient error), also log it:
"$_AK/bin/analyst-kit-learn" add '{"skill":"sec-filings","type":"pitfall|pattern|preference","ticker":"<optional>","insight":"<one line>","confidence":7,"ts":"<iso8601 utc>"}'