To get alerted on new SEC filings, request the latest filings for each form group once a day, skip the filing ids you have already seen and alert on the rest. Narwhal returns SEC EDGAR filings with a form, a type, a filing date and a stable id.
Request the latest filings by type
GET /v1/filings/latest?country=USA lists the newest SEC EDGAR filings. The type parameter selects a group of forms, and limit goes up to 200. Schedule 13D and 13G ownership reports are in the ownership group, 8-K current reports are in event, and 10-K annual reports are in annual. This request asks for the newest ownership filings.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/filings/latest?country=USA&type=ownership&limit=2"{
"filings": [
{"id": "usa:0000049754:0001213900-26-115110", "filed": "2026-11-03", "form": "SCHEDULE 13D", "type": "ownership", "company_id": "usa:0000049754"},
{"id": "usa:0000049754:0001104659-26-121599", "filed": "2026-11-03", "form": "SCHEDULE 13D", "type": "ownership", "company_id": "usa:0000049754"}
]
}Each filing carries its id, the filed date, the form and the company_id. The latest endpoint has a type filter but no form filter, so one request returns every form in the group. Amendments appear under their own form name: the 200 newest ownership filings held SCHEDULE 13D, SCHEDULE 13D/A, SCHEDULE 13G and SCHEDULE 13G/A. Keep the form values you want and drop the rest in your script.
Alert on one company
To watch a single company, use GET /v1/companies/{company_id}/filings. It accepts a form filter and a filed_from date, so a request can ask for one company's 8-K filings since the start of a period. The company id is usa: followed by a ten-digit number, and every company_id in a latest-filings response is already in that form, so you can pass it straight in.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/companies/usa:0000320193/filings?form=8-K&filed_from=2026-01-01&limit=3"{
"id": "usa:0000320193",
"filings": [
{"id": "usa:0000320193:0000320193-26-000024", "filed": "2026-11-02", "form": "8-K", "type": "event", "period_end": "2026-11-02"},
{"id": "usa:0000320193:0000320193-26-000018", "filed": "2026-07-30", "form": "8-K", "type": "event", "period_end": "2026-07-30"},
{"id": "usa:0000320193:0000320193-26-000011", "filed": "2026-04-30", "form": "8-K", "type": "event", "period_end": "2026-04-30"}
]
}An empty filings list is a valid answer. The same company returned an empty list for SCHEDULE 13D. Page through a longer list with next_cursor, which the response returns when more rows exist. GET /v1/filings/{filing_id} opens one filing and returns its url on the SEC website, which is the link to put in the alert.
Poll once a day and dedupe by filing id
Every filing has a stable id, so a script can remember what it has already reported. The script below requests the three groups, keeps the forms in WATCH, skips every id saved in seen_filings.json, prints the rest and saves the updated set. Replace the print with a call that sends an email or a chat message.
import json, os, urllib.request
API = "https://api.narwhalapi.com"
WATCH = {"ownership": {"SCHEDULE 13D", "SCHEDULE 13D/A"}, "event": {"8-K"}, "annual": {"10-K"}}
SEEN_FILE = "seen_filings.json"
def get(path):
req = urllib.request.Request(API + path, headers={"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"})
return json.load(urllib.request.urlopen(req))
seen = set(json.load(open(SEEN_FILE))) if os.path.exists(SEEN_FILE) else set()
new = []
for kind, forms in WATCH.items():
for f in get(f"/v1/filings/latest?country=USA&type={kind}&limit=200")["filings"]:
if f["form"] in forms and f["id"] not in seen:
new.append(f)
seen.add(f["id"])
for f in new:
print(f["filed"], f["form"], f["company_id"], f["id"]) # send an email or chat message here
print(len(new), "new filings")
json.dump(sorted(seen), open(SEEN_FILE, "w"))2026-11-03 SCHEDULE 13D usa:0000049754 usa:0000049754:0001213900-26-115110
2026-11-03 SCHEDULE 13D usa:0000049754 usa:0000049754:0001104659-26-121599
2026-11-03 SCHEDULE 13D usa:0002132873 usa:0002132873:0001104659-26-121596
293 new filings0 new filingsThe second run found nothing because every id was already in the file. A filing that arrives after the first run gets an id the file has never held, so it prints once and is then added. An amendment has its own id, so a SCHEDULE 13D/A prints as a new alert even when the original 13D was reported earlier.
Run it on a schedule
A daily cron entry is enough for a daily alert. It loads the key from an environment file, runs the script and appends its output to a log.
15 1 * * * . $HOME/.narwhal.env && cd /home/me/sec-alerts && /usr/bin/python3 watch_sec.py >> watch.log 2>&1Choose limit and the schedule together. Each request returns at most 200 filings per group, and the first run above printed 293 new filings across the three groups. The 200 newest ownership filings reached back to 2026-10-30. A daily run leaves a wide margin; a weekly run could miss filings that have already moved past the 200 newest. If a run fails, run it again: the seen file only changes after the requests succeed, so nothing is lost. The daily data pull guide covers cron, logging and failed runs.
Which request for which alert?
| You want to know about | Request | Match on |
|---|---|---|
| A new Schedule 13D or 13D/A | type=ownership on the latest endpoint | form is SCHEDULE 13D or SCHEDULE 13D/A |
| A new 8-K from any company | type=event | form is 8-K |
| A new 10-K from any company | type=annual | form is 10-K |
| Any 8-K from one company | Company filings with form=8-K | The returned id |
Why dedupe by id and not by filing date?
Many filings share a date, and a filing can appear after the date you last checked. The id names one filing and never repeats, so a set of ids gives an exact answer to whether you have seen a filing. A date would either repeat alerts or skip filings that arrive late.
Can I filter by form on the latest endpoint?
No. The latest endpoint filters by country, type, limit and lang. Use type to get the right group and match form in your script. The company filings endpoint does accept form, along with filed_from, filed_to and include_amendments.
Does a foreign company file a 10-K?
The annual group holds more than 10-K filings. The newest annual filings included 20-F and 20-F/A, which foreign issuers file. Keep 10-K in your match set when you want the US annual report only.
Published .