3spread
All guides

Track activist investors with 13D and 13G filings

Pull Schedule 13D and 13G filings for a company, tell activist stake-building apart from passive index ownership, and read the reporting persons behind a stake. Python, on a free key.

The 3spread TeamJuly 14, 2026Beneficial ownership dataset

When somebody crosses 5 percent ownership of a public company, they have to say so. The form they use tells you almost everything about their intent.

Schedule 13D is the activist filing. You file it when you might actually do something: push for board seats, agitate for a sale, run a proxy fight. It comes with a deadline measured in days and a requirement to describe your purpose.

Schedule 13G is the passive one. Index funds and long-only managers who happen to own more than 5 percent file it, and it says, in effect, we are just holding this.

Same threshold, very different signal. A 13D landing on a mid-cap is news. A 13G from Vanguard is Tuesday. This guide pulls both and tells them apart.

Everything below runs on the free Community tier: every dataset, 10,000 requests per day (600 per minute), no credit card.

Setup

pip install py3spread
export THREESPREAD_API_KEY="sk_live_..."

Recent ownership filings for a company

import itertools
from py3spread import Client
 
TICKER = "AAPL"
 
with Client() as client:
    filings = list(itertools.islice(client.beneficial_ownership.iter(ticker=TICKER), 10))
    if not filings:
        print(f"no 13D/13G filings for {TICKER}")
        raise SystemExit
 
    print(f"recent beneficial ownership filings for {TICKER}:")
    for f in filings:
        kind = f.get("schedule_type") or f["form_type"]
        amended = " (amendment)" if f["is_amendment"] else ""
        event = f" event {f['event_date']}" if f.get("event_date") else ""
        print(f"  {f['accepted_time'][:10]}  {kind:<6}{event}{amended}")

schedule_type is the field that matters: it is what separates a 13D from a 13G. Falling back to form_type covers the filings where the schedule is not stated separately.

Two other fields worth understanding. is_amendment tells you this is an update to an existing position rather than a new one, and amendments are where the interesting movement lives: an activist raising their stake, or exiting. event_date is the date the ownership event actually happened, which is not the same as when it was filed. That gap is the reporting lag, and it can be days.

Who is actually behind the stake

The filing detail carries the reporting persons, which is where the names are:

    detail = client.beneficial_ownership.get(filings[0]["filing_id"])
    print(f"\nlatest filing: {detail['filing_id']}")
 
    for p in detail.get("reporting_persons") or []:
        pct = p.get("percent_of_class")
        pct = f"{float(pct):.1f}% of class" if pct else "pct not stated"
        print(f"  {p['names']}  ({pct}, source of funds {p.get('source_of_funds') or '?'})")
 
    print(f"source: {detail['source_url']}")

percent_of_class is the size of the stake. source_of_funds is a small field that occasionally says a lot: it is a coded hint about where the money came from, and a stake funded by borrowings reads differently from one funded from a fund's working capital.

Note the defensive .get() calls and the or []. Beneficial ownership filings are less rigidly structured than Form 4s, and not every filer populates every field. Code that assumes all of them are present will break on real data.

source_url points at the original filing on EDGAR, which is worth keeping in anything you build. When a number looks strange, you want to be one click from the document that produced it.

Where to take it

Drop the ticker filter and iterate the whole family, keeping only rows where schedule_type is 13D and is_amendment is false, and you have an activist alert: every new activist position being opened, across the market, as it is disclosed.

Pair it with the insider transactions dataset and you can ask a sharper question: when an activist shows up, do the insiders start buying alongside them, or quietly selling into the rally?

The dataset

Full endpoint reference and response schemas are on the beneficial ownership dataset page.

Run this yourself

Everything here works on the free Community tier: every dataset, 10,000 requests per day (600 per minute), no credit card. Provision a key and paste the code in.