How to get SEC SC 13D activist stakes as JSON (without parsing EDGAR yourself)
When an investor crosses 5% of a company's voting stock with intent to influence it, they have ten days to file a Schedule 13D. That filing is the public record of an activist campaign starting: Starboard taking a position, Elliott building a stake, a founder consolidating control. If you trade event-driven or build research tooling, you want those as structured records the moment they land. SEC EDGAR will not hand them to you that way. This walks through why, and how to skip the parsing entirely.
Why 13D is annoying to get as data
The first surprise: a 13D is filed under the holder's CIK, not the company's.
So if you poll a target ticker's submissions feed waiting for an activist, you
will miss the initial 13D — it shows up on the reporting person's filing history,
and the issuer is only listed as the subject. To catch every new stake you have
to watch the form type across all filers, which means EDGAR full-text search
(efts.sec.gov), not a per-company scan.
The second surprise is the form-type string itself. The submissions API and the
filing cover page call it SC 13D. The full-text-search index calls the same
form SCHEDULE 13D. Query with the wrong one and you get zero hits and no error.
The third surprise is the payload. Once you find the filing, the numbers you
actually want — percent of class, shares beneficially owned, who the reporting
person is — live on the cover page. Newer filings carry a primary_doc.xml with
real elements (reportingPersonInfo, percentOfClass, aggregateAmountOwned,
soleVotingPower). Older ones bury the same facts in narrative text like
Percent of class represented by amount in Row (11): 5.1%. You end up writing
one parser for the structured schema and a regex fallback for the prose, then
maintaining both as the SEC rotates schema versions.
None of this is hard. It is just fiddly, and it breaks quietly. That is the worst kind of data plumbing to own.
The shortcut: one GET request
EDGAR Events does that pipeline and hands you the result. The activist-stakes endpoint resolves the full-text search, follows each hit to its cover-page XML, and extracts holder, target, percent of class, and share count into a typed record.
curl -s -H "X-API-Key: $EDGAR_KEY" \
"https://api.edgarevents.com/activist-stakes?min_percent=5&limit=10"
A single event looks like this (a real SC 13D/A pulled from the live feed):
{
"event_id": "0001493152-26-029689",
"event_type": "activist_stake",
"form": "SCHEDULE 13D/A",
"filed_date": "2026-06-23",
"accession": "0001493152-26-029689",
"target": {
"name": "AST SpaceMobile, Inc.",
"ticker": "ASTS",
"tickers": ["ASTS"],
"cik": "0001780312"
},
"holders": [
{
"name": "Abel Avellan",
"cik": "0001680225",
"shares": 78252625.0,
"percent_of_class": 20.8,
"sole_voting": 78252625.0,
"shared_voting": 0.0,
"type_of_reporting_person": "IN"
}
],
"percent_of_class": 20.8,
"shares": 78252625.0,
"security_class": "Class A Common Stock",
"date_of_event": "06/22/2026",
"filing_url": "https://www.sec.gov/Archives/edgar/data/1680225/000149315226029689/primary_doc.xml",
"parsed_structured": true
}
The top-level percent_of_class and shares are the largest values across the
reporting persons, so a single-number filter does what you expect. Every holder
is also itemized under holders if you need the breakdown. When a filing is too
old to carry structured XML, parsed_structured is false and you get a
percent_narrative field with the raw cover-page text instead of guessed
numbers — the service does not fabricate a percent it could not read.
Filtering down to what you trade
The endpoint takes the filters you would otherwise apply by hand:
# Only a specific target
curl -s -H "X-API-Key: $EDGAR_KEY" \
"https://api.edgarevents.com/activist-stakes?ticker=ASTS"
# Skip amendments, stakes of 10%+ only, in a date window
curl -s -H "X-API-Key: $EDGAR_KEY" \
"https://api.edgarevents.com/activist-stakes?include_amendments=false&min_percent=10&startdt=2026-06-01&enddt=2026-06-26"
include_amendments controls whether 13D/A amendments (a holder updating an
existing position) come through alongside fresh 13D filings. min_percent
filters on percent of class. ticker narrows to one target.
Getting them pushed instead of polled
Polling is fine for backfill. For live campaigns you want the filing the moment it clears, so register a webhook and let the service POST matching events to you:
curl -s -X POST -H "X-API-Key: $EDGAR_KEY" -H "Content-Type: application/json" \
-d '{"event_types":["activist_stake"],"min_percent":5,"url":"https://your-app.com/hooks/edgar"}' \
"https://api.edgarevents.com/webhooks"
Each delivery is signed with HMAC-SHA256 in an X-Edgar-Signature: sha256=...
header so you can verify it came from the service. Sign the raw request body with
your webhook secret and compare:
import hmac, hashlib
def verify(secret: str, raw_body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
The webhook body wraps the same event record under
{"type": "filing.created", "event": {...}}.
When to build it yourself instead
To be straight about it: if you only need 13D data occasionally and do not mind
running the pipeline, open-source libraries like edgartools and py-sec-edgar
parse EDGAR well and cost nothing but your time and a cron box. The reason to pay
for this is that you would rather not own the full-text-search quirks, the
two-schema cover-page parser, the SEC rate-limit throttling, and a webhook
delivery loop — and you want a signed push within seconds of a filing rather than
a nightly batch. That is the entire pitch: a narrow, hosted, real-time slice of
EDGAR for people whose product is the thing they build on top of the data, not
the scraper.
It is $29/month, self-serve, cancel anytime. Live from data.sec.gov. Get a key
at edgarevents.com and the interactive reference is at
api.edgarevents.com/docs.
SEC filings, already parsed.
Typed JSON for 8-K item codes, SC 13D activist stakes, IPO forms and merger proxies. $29/mo, self-serve, cancel anytime.