edgrapi
SEC EDGAR filings as clean JSON. Zero dependencies.
pip install edgrapi
Nothing else gets installed. No requests, no pandas — the client is standard library only, so it drops into a Lambda, a container, or someone else's dependency tree without an argument.
from edgrapi import Client
c = Client("edgr_...") # or set EDGRAPI_KEY
c.holdings("berkshire") # 13F, CUSIP-aggregated, diffed QoQ
c.insider("AAPL") # Form 4, code P separated from 10b5-1
c.clusters(days=15, min_insiders=3) # several insiders buying at once
c.search("climate risk", forms="10-K") # full text, every filing since 2001
Or without writing any Python at all:
export EDGRAPI_KEY=edgr_...
edgrapi holdings berkshire --changes
edgrapi clusters --days 15 --min-insiders 3
edgrapi insider AAPL --limit 5 | jq '.transactions[0]'
Get a free key — 100 credits every month, no card — at edgrapi.com/app.
Why this exists
The SEC's data is free. Parsing it correctly is the work, and most of the bugs are silent:
- A 13F lists one holding once per sub-manager. Berkshire's latest filing has 89 rows and 29 actual positions — Apple alone appears 12 times. Read the table as it arrives and you triple-count the portfolio.
- A
putCallrow is a bet against the stock, not a holding. Every quarter someone reports a fund is "long" a name it is actually short. - Filings before 2023 report value in thousands; newer ones in whole dollars. Diff across that boundary without normalising and every position looks like it grew 1000×.
- Share classes are separate CUSIPs. Alphabet is
02079K305(Class A) and02079K107(Class C). Merging them is as wrong as splitting Apple. - XBRL tags drift between filers and across years, so the same line item arrives under a dozen different names.
holdings() handles all of that and labels every position new / added / reduced / exited against the prior quarter, so you get what changed rather than one quarter's snapshot.
What you can call
| Method | What it returns |
|---|---|
company(ticker) |
CIK, legal name, SIC industry, fiscal-year end |
filings(ticker, form=, limit=) |
Filings newest first, filterable by form |
resolve(ticker=, cik=, cusip=) |
Map any identifier to the others |
subsidiaries(ident) |
Subsidiaries from the 10-K Exhibit 21 |
fundamentals(ticker, period=) |
Income statement, balance sheet, cash flow |
ratios(ticker) |
Margins, returns, leverage, liquidity |
xbrl(ident, concept=) |
Raw XBRL as JSON, optionally one tag |
shares(ticker, history=) |
Shares outstanding and public float |
sections(ticker, item=) |
Risk factors, MD&A, business — from 10-K/10-Q |
insider(ticker, ...) |
Form 4 transactions |
clusters(days=, min_insiders=) |
Market-wide cluster buys |
form144(ticker) |
Notices of proposed sale |
events(ticker, notable=) |
8-K material events, item-coded |
restatements() / auditor_changes() |
Market-wide feeds |
activist(identifier, activist_only=) |
13D/13G stakes above 5% |
holdings(identifier, changes=) |
13F holdings, aggregated and diffed |
formd(ident) |
Form D private placements |
search(q, forms=, startdt=) |
Full-text search since 2001 |
download(accession) |
Raw filing document (read .text / .content, not as a dict) |
Anything added after this release is reachable with c.get("/v1/whatever", param=...).
Credits, errors and retries
Every response is the API's JSON dict with billing attached:
r = c.insider("AAPL")
r["count"] # the data
r.credits_cost # what this call was billed
r.credits_remaining # balance afterwards
Calls cost 1, 2, 3 or 5 credits depending on the endpoint — a light lookup is 1, an XBRL parse is 3, pulling a full document is 5. A hard error refunds in full. A valid response that carried no rows keeps a 1-credit floor, on the endpoints that report a count.
Errors are typed, so you can branch on them:
from edgrapi import Client, OutOfCredits, RateLimited, EdgrapiError
try:
c.holdings("berkshire")
except OutOfCredits as e:
print("needed", e.endpoint_cost, "credits")
except RateLimited as e:
time.sleep(e.retry_after or 5)
except EdgrapiError as e:
print(e.status, e.error, e.request_id)
429s and 5xx are retried automatically with exponential backoff, honouring Retry-After. A 404 is never retried — it will not start working.
Worth knowing
holdings()takes a fund, not a ticker — a name likeberkshire, a CIK, or a filer ticker.activist()andformd()accept"latest"for the market-wide feed.- Only open-market purchases (code P) mean an insider spent their own money. Option exercises and vesting also appear on Form 4 as acquisitions but say nothing; most sales are pre-scheduled 10b5-1.
- Data is public-domain SEC EDGAR. Nothing here is investment advice.
Also available
The same data is exposed as a hosted MCP server at https://api.edgrapi.com/mcp, so an agent in Claude, Cursor or Cline can call it directly:
{ "mcpServers": { "edgrapi": {
"url": "https://api.edgrapi.com/mcp",
"headers": { "Authorization": "Bearer edgr_YOUR_KEY" } } } }
Full REST docs at edgrapi.com/docs · OpenAPI at api.edgrapi.com/openapi.json
License
MIT
Metadata
Release files for edgrapi 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| edgrapi-1.0.0.tar.gz | 17.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| edgrapi-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.2 kB
Release files / edgrapi-1.0.0.tar.gz
| Download URL | edgrapi-1.0.0.tar.gz |
|---|---|
| Size | 17.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68c82b8e1739adbf9c9d4a40dc3ce488cf5437432e54a5cfc06541ec7e652d33
|
|
BLAKE2b-256 checksum How to use checksums |
a28bcdb3f3fa74c90b37f573ee87fe7a4e70f1357244a8681da0e5e530084440
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / edgrapi-1.0.0-py3-none-any.whl
| Download URL | edgrapi-1.0.0-py3-none-any.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fb74c86edce056890f86fd4c0f0dfac93c41eccde8ea1cb5597deff730623ae1
|
|
BLAKE2b-256 checksum How to use checksums |
e1ee8600cf5e39e0f111f9e104ed777d87c322536b0ccde91be704f845904357
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|