Get each day's new Form D filings from SEC EDGAR, filtered by industry, state and round size, in Python or Slack.
Written with AI assistance and reviewed by the FedSignals Data team; we maintain these tools.
When a U.S. company raises money privately, whether it's a startup's seed round, a Series A, a real-estate syndication or a fund, it files a Form D with the SEC, within 15 days after the first sale of securities. The filing lists the issuer, its state and industry, the offering size, how much has been sold and how many investors bought in. It's public on SEC EDGAR the same day.
That makes Form D one of the earliest public signals that a company just raised money. Sales teams, investors and recruiters all watch it.
The usual options for getting the data:
- Search EDGAR by hand. It's free, but you can't filter by round size or industry, and you have to open each filing.
-
Parse EDGAR yourself. Download the daily index, find the
Dentries, fetch eachprimary_doc.xmland parse it, while following the SEC's fair-access rules (a declared User-Agent, at most 10 requests per second). This dev.to walkthrough is a good start if you want to build it yourself. - Use a paid filings API such as sec-api.io, which covers every form type.
We built the SEC Form D Scraper: EDGAR Startup Funding Rounds Actor on Apify as a middle option, and this tutorial walks through it. It does the index-and-parse work for you, applies your filters, and returns only filings you haven't seen before.
What you'll build
- A daily list of new Form D filings, filtered by industry group, issuer state and offering size.
- Operating companies only, with funds and SPVs removed.
- A daily run that returns only filings it hasn't delivered before.
- A Slack message (or webhook to your CRM) with each new round.
Step 1: Run it once by hand
Open apify.com/fedsignals/sec-form-d-monitor and click Try for free. This is the exact input we used for this article:
{
"daysBack": 3,
"industryGroups": ["Other Technology", "Biotechnology"],
"excludePooledFunds": true,
"minOfferingAmount": 1000000,
"onlyNew": false,
"maxItems": 5
}
What each setting does:
-
daysBack: 3reads today's EDGAR daily index plus the previous business days. Use1for a daily schedule. -
industryGroupsuses the industry list from the Form D itself, such as "Other Technology", "Biotechnology", "Pharmaceuticals", "Other Health Care" or "Computers". Leave it empty for all industries. -
excludePooledFunds: truedrops VC, PE and hedge funds and SPVs, which make up a large share of Form D filings. You get operating companies only. -
minOfferingAmount: 1000000keeps offerings of $1M or more. Issuers can report the size as "Indefinite". Those rows are kept unless you setkeepIndefiniteAmounts: false. -
onlyNew: falseis for this first test. You'll turn it on in Step 3.
Other filters: issuerStates (two-letter codes such as CA, NY, TX), maxOfferingAmount, incorporatedWithinFiveYearsOnly (a good proxy for young startups) and includeAmendments for Form D/A filings.
Step 2: Look at the output
Our run on Oct 29, 2026 took about 10 seconds. Its status read 5 new Form D filings delivered (maxItems 5 reached). Here's one row, trimmed:
{
"issuerName": "Arms Cyber Defense, Inc.",
"issuerCity": "Los Gatos",
"issuerState": "CA",
"jurisdictionOfInc": "DELAWARE",
"entityType": "Corporation",
"yearOfInc": null,
"industryGroup": "Other Technology",
"exemptions": ["06b"],
"isAmendment": false,
"dateOfFirstSale": "2026-10-13",
"securitiesTypes": ["Equity"],
"totalOfferingAmount": 9960000,
"currency": "USD",
"offeringAmountIndefinite": false,
"totalAmountSold": 9960000,
"totalRemaining": 0,
"totalInvestors": 38,
"accessionNumber": "0002161341-26-000001",
"filingDate": "2026-10-28",
"filingIndexUrl": "https://www.sec.gov/Archives/edgar/data/2161341/000216134126000001/0002161341-26-000001-index.htm"
}
All five filings were filed on Oct 28, 2026:
| Issuer | State | Offering | Sold | Investors |
|---|---|---|---|---|
| Accufy Diagnostics, Inc. | MD | $2.0M | $2.0M | 3 |
| Arcline Systems, Inc. | TN | Indefinite | $75K | 2 |
| Arms Cyber Defense, Inc. | CA | $9.96M | $9.96M | 38 |
| BIOMETRICA HEALTH, INC. | MN | $2.0M | $20K | 1 |
| CVAT.ai Corp | DE | $5.5M | $2.5M | 2 |
Two things to notice:
-
Arcline's offering size is
null. The issuer reported it as Indefinite, soofferingAmountIndefiniteistrue. Arms Cyber'syearOfIncis alsonullbecause the issuer didn't give a year. Your code should handle both (see Step 3). - "Sold" can be much less than "Offering". A Form D is often filed after the first sale, while the round is still open. The gap tells you how much is still being raised.
The run also writes a summary to its key-value store (record SUMMARY):
{
"indexEntries": 389,
"filingsChecked": 34,
"pushed": 5,
"skippedAlreadySeen": 0,
"parseErrors": 0
}
filingsChecked is how many Form D documents the Actor fetched and parsed before it reached maxItems. parseErrors counts filings it couldn't read. Newer builds also list indexDaysRead and indexDaysNotPublished, so you can see which EDGAR days were covered. Today's index usually appears in the evening, US Eastern, so a run before then reads only the earlier business days. In a wider test (daysBack: 1, five tech and health industry groups, operating companies, $1M+), Oct 28 alone produced 11 matching filings out of 142 checked.
Step 3: Call it from Python
This runs the Actor synchronously and prints each new filing. It reads your Apify token from an environment variable (never paste it into code). maxTotalChargeUsd caps the run's charges.
import os
import requests
ACTOR = "fedsignals~sec-form-d-monitor"
URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items"
run_input = {
"daysBack": 3,
"industryGroups": ["Other Technology", "Biotechnology"],
"excludePooledFunds": True,
"minOfferingAmount": 1000000,
"onlyNew": True, # only filings this stateKey hasn't returned before
"stateKey": "tech-bio-1m-rounds", # one key per saved search
"maxItems": 4,
}
resp = requests.post(
URL,
params={"maxTotalChargeUsd": 0.05},
headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
json=run_input,
timeout=300,
)
resp.raise_for_status()
def money(x):
return f"${x:,.0f}" if x is not None else "indefinite"
for n in resp.json():
investors = n.get("totalInvestors") or 0
print(
f"{n['filingDate']} {n['issuerName']} ({n.get('issuerState') or '??'}) "
f"offering {money(n.get('totalOfferingAmount'))}, sold {money(n.get('totalAmountSold'))}, "
f"{investors} investor{'' if investors == 1 else 's'} {n['filingIndexUrl']}"
)
Run it twice. The second run returns the next filings, not the same ones: onlyNew remembers every filing delivered for that stateKey.
Step 4: Schedule it daily
EDGAR posts each day's index in the evening (U.S. Eastern), so run it once a weekday night.
- In the Apify console, click Save as a new task, set
daysBack: 1,onlyNew: trueand astateKey. - Go to Schedules → Create new, use the cron expression
0 22 * * 1-5with the timezone set toAmerica/New_York, and attach the task.
If a run is missed, raise daysBack for one run to catch up. onlyNew prevents duplicates.
Step 5: Send new rounds to Slack or a webhook
Apify has a built-in Slack integration (Integrations → Slack on your task). For one line per filing, add a webhook for ACTOR.RUN.SUCCEEDED that points at a small function you host. The payload includes resource.defaultDatasetId:
import os
import requests
def handler(payload: dict) -> None:
dataset_id = payload["resource"]["defaultDatasetId"]
items = requests.get(
f"https://api.apify.com/v2/datasets/{dataset_id}/items",
headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
timeout=60,
).json()
if not items:
return # no new filings today
lines = [f"*{len(items)} new Form D filings*"]
for n in items[:25]:
sold = n.get("totalAmountSold")
offered = n.get("totalOfferingAmount")
size = f"${offered / 1e6:,.1f}M offered" if offered else "size indefinite"
raised = f", ${sold / 1e6:,.2f}M sold" if sold else ""
lines.append(
f"• <{n['filingIndexUrl']}|{n['issuerName']}> ({n.get('issuerState') or '??'}, "
f"{n.get('industryGroup') or 'n/a'}): {size}{raised}"
)
requests.post(os.environ["SLACK_WEBHOOK_URL"], json={"text": "\n".join(lines)}, timeout=30)
The same webhook can feed Google Sheets, HubSpot, Zapier or Make.
What it costs
The Actor uses pay-per-event pricing: $0.006 per filing delivered ($6 per 1,000), plus Apify's $0.00005 run-start fee. The 5-filing test above costs about $0.03. A daily run that matches about 10 filings costs about $0.06 a day. A night with no new matches costs only the start fee.
FAQ
Do I need an SEC API key?
No. EDGAR is public and keyless. The Actor sends the declared User-Agent the SEC requires and stays under its rate limit. You only need a free Apify account.
Does it return founders' or executives' names?
No. Form D lists "related persons" with names and addresses. The Actor drops them on purpose, along with the issuer's phone number. You get company-level data only.
Is a Form D proof that a company raised money?
It's a notice filed by the issuer, and the SEC doesn't review it. totalAmountSold is what the issuer reported at filing time. Treat it as a strong signal, not audited data.
What do the exemption codes mean?
06b is Rule 506(b), 06c is Rule 506(c) (general solicitation allowed), and 04 is Rule 504.
Can I include funds?
Yes. Leave excludePooledFunds off, or set industryGroups to ["Pooled Investment Fund"] to get only funds.
Is this investment advice?
No. It's a feed of public filings.
Related
- How to Get Daily SAM.gov Contract Opportunity Alerts in Slack: the same pattern for federal contract opportunities.
- How to Find Expiring Federal Contracts (Recompetes) 6–18 Months Early with Python: the same pattern for B2G leads before the solicitation posts.
Last updated: Nov 5, 2026 (tested against Actor build 0.1.16).
FedSignals Data builds small public-data monitors on Apify. FedSignals is not affiliated with, or endorsed by, the U.S. Securities and Exchange Commission. Questions or feature requests: fedsignals.alerts@gmail.com.
Top comments (0)