An insider trading data API gives you SEC Form 4 filings as structured JSON instead of raw EDGAR documents. Every officer, director, and 10 percent owner of a US public company has to report trades in their own company's stock within two business days, and those reports are public. The catch is the format. EDGAR serves XML with footnotes, amendments, and a one-letter transaction code system that reads as noise until you learn it. This post covers how to pull Form 4 transactions for any US ticker through one REST endpoint, what the codes actually mean, and a short Python screen that separates deliberate open-market buys from routine compensation activity.
What a Form 4 transaction code actually means#
Every transaction on a Form 4 carries a single-letter code, and the code changes the meaning of the row entirely. The ones you'll see most:
- P: open-market or private purchase. The insider paid their own cash at or near the market price.
- S: open-market or private sale.
- A: grant or award. Scheduled compensation rather than a market decision.
- M: exercise of options or conversion of a derivative security.
- F: shares withheld by the company to cover taxes on vesting equity.
- G: a bona fide gift.
There are a few rarer ones (C for conversion, D for disposition to the issuer, X for expiring options, J for "other"), but P, S, A, M, and F cover the bulk of filings.
Here's the distinction that matters for research. Code P is the only code recording a voluntary decision to spend cash on stock at market price. Code A is payroll. Code F looks like a sale in the raw data, but the insider made no decision at all; the company withheld shares to cover a tax bill. Even code S is ambiguous on its own, because a large share of insider sales are the back half of a same-day exercise-and-sell (an M row followed by an S row) or scheduled 10b5-1 plan sales. If a screen treats every S as bearish and every A as bullish, it's measuring vesting schedules rather than conviction.
Pulling Form 4 transactions from the insider trading data API#
SiftingIO exposes Form 3, 4, and 5 transactions per ticker under the fundamentals endpoint family:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/fnd/stocks/AAPL/insiders?limit=25"
Tickers are case-insensitive. The endpoint paginates with a cursor: the default page size is 10 and the max is 25, and each response carries meta.next_cursor (null on the last page) along with total and as_of. Each row includes the filing date, the transaction code, share counts, and price where the filing reports one; see /docs for the full field list.
Building a watchlist screen in Python#
A watchlist screen is the same fetch in a loop: page through each ticker's transactions, stack them into one DataFrame, then filter on the code.
import os
import requests
import pandas as pd
BASE = "https://api.sifting.io/v1"
HEADERS = {"X-API-Key": os.environ["SIFTING_KEY"]}
WATCHLIST = ["AAPL", "MSFT", "NVDA", "JPM"]
def fetch_insiders(ticker):
rows, cursor = [], None
while True:
params = {"limit": 25}
if cursor:
params["cursor"] = cursor
r = requests.get(f"{BASE}/fnd/stocks/{ticker}/insiders",
headers=HEADERS, params=params)
r.raise_for_status()
body = r.json()
rows.extend(body["data"])
cursor = body["meta"]["next_cursor"]
if cursor is None:
break
return rows
frames = []
for t in WATCHLIST:
df = pd.DataFrame(fetch_insiders(t))
df["ticker"] = t
frames.append(df)
txns = pd.concat(frames, ignore_index=True)
# Open-market purchases only; match the column name to the /docs schema
buys = txns[txns["code"] == "P"]
print(buys.groupby("ticker").size().sort_values(ascending=False))
From here the useful aggregations are cheap. Group buys by ticker and month to spot cluster buying, where several insiders purchase within a short window. Count distinct filers instead of raw rows, since one insider splitting a purchase across three days shouldn't look like three independent decisions. For price context, pull daily bars from /v1/hist/stocks/AAPL/bars?interval=1d and join on date; the bars endpoints require gzip, which the requests library negotiates automatically through its default Accept-Encoding header.
One framing note: a cluster of code P purchases is a research input. It isn't a trade signal on its own. Insiders are wrong plenty, and they buy for reasons (rebalancing, optics, contractual requirements) that have nothing to do with a view on the stock. The value of the data is that it's a clean, timestamped record of decisions, which you can test against forward returns yourself.
Common pitfalls#
Treating every S row as bearish. Before reading anything into a sale, check for an M row with the same date and a matching share count. An exercise-and-sell pair is compensation processing. The same goes for F rows: they mechanically reduce the insider's position, but no sell decision happened. Filter both out before computing any buy/sell ratio.
Assuming the usual pagination limits. Most SiftingIO list endpoints default to 50 rows and accept limit up to 200. The insiders endpoint is the exception: default 10, max 25. Send limit=100 and the API returns 400 invalid_parameter; it doesn't silently truncate the page. Loop on meta.next_cursor rather than raising the limit.
Burning your rate limit on a big watchlist. The free tier allows 60 requests per minute, and a 40-ticker watchlist at three pages each is 120 requests. Watch the X-RateLimit-Remaining header as you loop, and on a 429 the Retry-After header says how many seconds to wait. For a nightly batch job, spacing requests one second apart keeps you clear of the ceiling without meaningfully slowing the run.
Where to go from here#
Form 4 data pairs naturally with the rest of the fundamentals family: /v1/fnd/stocks/{ticker}/ownership for Schedule 13D/13G beneficial ownership, /v1/fnd/filers/{filer}/holdings for 13F institutional positions, and /v1/fnd/stocks/{ticker}/filings for the underlying documents. All of it sits behind the same API key and the same cursor pagination, so the screen above extends to those datasets without new plumbing. The free tier covers 10,000 calls a month, enough for a nightly watchlist refresh. Start building free



