Python SEC EDGAR API examples

Working Python code for the most common SEC EDGAR queries: insider trades, institutional holdings, real-time 8-K events. Uses only the requests library, no SDK required.

Want this data via API instead of reading about it? Get a free API key →

The problem

You need to pull SEC filings from Python. You don't want to write XML parsers, manage SEC rate limits, or build a CUSIP-to-ticker mapping. You also don't want to install heavy SDKs for a few simple HTTP calls.

EdgarKit's API works with plain requests. The whole integration is environment variable + HTTP call.

The approach

We'll walk through five common queries in Python, each one a complete working snippet:

  1. Recent Form 4 insider purchases
  2. All filings for a specific ticker
  3. Cluster buy detection
  4. 13F holdings for a specific manager
  5. Real-time 8-K monitoring with webhooks

Setup

Install requests if you don't have it:

pip install requests

Set your API key as an environment variable:

export EDGARKIT_API_KEY="your_api_key_here"

Sign up at edgarkit.com to get one. The free tier covers 1,000 requests per month.

Example 1: Recent Form 4 insider purchases

import os
import requests

API_KEY = os.environ["EDGARKIT_API_KEY"]
BASE = "https://api.edgarkit.com"

resp = requests.get(
    f"{BASE}/v1/filings",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={
        "form_type": 4,
        "transaction_code": "P",   # P = open-market purchase
        "min_value": 100000,
        "limit": 20,
    },
    timeout=30,
)
resp.raise_for_status()

for f in resp.json()["data"]:
    print(
        f"{f['issuer_ticker']:6} "
        f"{f['reporter_name'][:30]:30} "
        f"${float(f['total_value']):>12,.0f}  "
        f"{f['transaction_date']}"
    )

This pulls the 20 most recent open-market purchases above $100,000.

Example 2: All filings for a specific ticker

import requests

resp = requests.get(
    "https://api.edgarkit.com/v1/filings",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={"ticker": "NVDA", "limit": 50},
    timeout=30,
)

for f in resp.json()["data"]:
    print(f"{f['form_type']:10} {f['filed_at']}  accession={f['accession_number']}")

You can filter further by form_type to scope to insider trades, earnings releases, etc.

Example 3: Cluster buy detection

A more substantial example. Pull recent open-market purchases, group by issuer, surface 3+ distinct-buyer clusters:

import os
import requests
from collections import defaultdict
from datetime import date, timedelta

API_KEY = os.environ["EDGARKIT_API_KEY"]

end = date.today()
start = end - timedelta(days=14)

resp = requests.get(
    "https://api.edgarkit.com/v1/filings",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={
        "form_type": 4,
        "transaction_code": "P",
        "min_value": 50000,
        "since": start.isoformat(),
        "limit": 1000,
    },
    timeout=30,
)
resp.raise_for_status()
filings = resp.json()["data"]

by_issuer = defaultdict(list)
for f in filings:
    by_issuer[f["issuer_cik"]].append(f)

print(f"Clusters with 3+ distinct buyers (14-day window):")
for cik, entries in by_issuer.items():
    buyers = {e["reporter_cik"] for e in entries}
    if len(buyers) >= 3:
        total = sum(float(e["total_value"]) for e in entries)
        print(
            f"  {entries[0]['issuer_ticker']:6} "
            f"{len(buyers)} buyers  ${total:>12,.0f}  "
            f"{entries[0]['issuer_name']}"
        )

For a complete production-ready cluster detector, see the cluster buying guide.

Example 4: 13F holdings for a manager

import requests

# Berkshire Hathaway's manager CIK is 1067983
resp = requests.get(
    "https://api.edgarkit.com/v1/filings",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={"form_type": "13F-HR", "manager_cik": 1067983, "limit": 1},
    timeout=30,
)
filing = resp.json()["data"][0]

print(f"Filing date: {filing['filed_at']}")
print(f"Holdings count: {len(filing['holdings'])}")
print()
print("Top 10 by value:")
top = sorted(filing["holdings"], key=lambda h: float(h["value_usd"]), reverse=True)[:10]
for h in top:
    print(f"  {h['ticker']:6} ${float(h['value_usd']):>15,.0f}  {h['issuer_name']}")

Each holding line includes the CUSIP, the resolved ticker, the issuer CIK, the value in USD, and the share count.

Example 5: Real-time 8-K monitoring

For real-time alerts, webhooks are the right tool. Register an endpoint, EdgarKit pushes each new filing to it within ~30 seconds of SEC acceptance:

import os
import requests

API_KEY = os.environ["EDGARKIT_API_KEY"]

resp = requests.post(
    "https://api.edgarkit.com/v1/webhooks",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "url": "https://your-server.com/edgarkit-events",
        "filters": {
            "form_types": ["8-K"],
            "items": ["5.02", "1.01", "2.01"],  # exec changes, M&A
        },
    },
    timeout=30,
)
print(resp.json())

On your receiving server (using Flask for the snippet):

import hmac
import hashlib
import os
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["EDGARKIT_WEBHOOK_SECRET"]

@app.post("/edgarkit-events")
def handle():
    signature = request.headers.get("X-EdgarKit-Signature", "")
    body = request.get_data()
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)

    filing = request.get_json()
    print(f"New 8-K: {filing['issuer_ticker']} item={filing.get('items')}")
    return "", 204

Always verify the signature. EdgarKit signs every webhook payload with HMAC SHA-256. The detailed pattern is in the webhook guide.

Putting it together

A minimal Python module you can drop into a project:

# edgarkit_client.py
import os
import requests

class EdgarKit:
    def __init__(self, api_key=None, base="https://api.edgarkit.com"):
        self.api_key = api_key or os.environ["EDGARKIT_API_KEY"]
        self.base = base

    def _get(self, path, **params):
        resp = requests.get(
            self.base + path,
            headers={"Authorization": f"Bearer {self.api_key}"},
            params=params,
            timeout=30,
        )
        resp.raise_for_status()
        return resp.json()["data"]

    def insider_purchases(self, ticker=None, min_value=100000, limit=50):
        return self._get(
            "/v1/filings",
            form_type=4,
            transaction_code="P",
            ticker=ticker,
            min_value=min_value,
            limit=limit,
        )

    def filings(self, **kwargs):
        return self._get("/v1/filings", **kwargs)

    def thirteen_f(self, manager_cik, limit=1):
        return self._get(
            "/v1/filings",
            form_type="13F-HR",
            manager_cik=manager_cik,
            limit=limit,
        )

# Usage:
# ek = EdgarKit()
# for f in ek.insider_purchases(ticker="NVDA"):
#     print(f["reporter_name"], f["total_value"])

Drop this into your codebase and call it as needed.

FAQ

Do I need an official SDK?

No. Plain requests is enough for almost all use cases. The API is REST + JSON. You can wrap it in a thin client class if your codebase prefers that pattern.

How do I handle rate limits?

EdgarKit returns HTTP 429 with a Retry-After header when you exceed your plan's rate limit. Catch the 429 in your code and wait the suggested number of seconds. The free tier is 10 requests per minute, Basic is 60, Pro is 300.

What about async / aiohttp?

The API is plain HTTP. Use aiohttp or httpx async clients the same way you would with any REST API. Nothing about EdgarKit is sync-only.

Can I use this in a Jupyter notebook?

Yes, and that's a common usage pattern. Set the API key once, then run cells that query for insider trades, holdings, or filings.

Where do I get an API key?

Sign up at edgarkit.com. Free tier requires email verification only, no credit card.