Most sales teams already know what a good trigger event looks like. A new chief executive reviews suppliers in the first few months. A company that has just bought another one has to merge two sets of systems, contracts and teams. A company that has signed a large customer agreement is about to scale up. The trouble is hearing about it in time, for the accounts you care about, without reading the news all day.
US public companies have to report these events to the Securities and Exchange Commission on Form 8-K, usually within four business days. The filings are public and free. What they lack is a format a sales team can use: each 8-K lists numbered "items" such as 5.02 or 2.01, and EDGAR shows them one filing at a time.
This tutorial shows how to turn those filings into a daily list of trigger events with plain-English labels and yes or no flags, filter it to your industries and accounts, and send alerts to Slack or email. It uses the 8-K Filings Scraper on the Apify Store, which I built.
What an 8-K item tells you
Every 8-K reports one or more numbered items. The Actor adds a short plain label and a category to each one. These are the items that matter most for sales:
| Item | Plain label | Why it matters |
|---|---|---|
| 1.01 | Signed a major agreement | New customers, suppliers, partnerships, credit deals |
| 2.01 | Completed an acquisition or sale of assets | New owners, integration projects, system changes |
| 5.01 | Change in control of the company | The company has new owners |
| 5.02 | Officer or director change | A new executive often reviews vendors |
| 1.03 | Bankruptcy or receivership | Distress signal for advisory and credit teams |
| 2.05 | Restructuring, exit or layoff costs | Cost cutting, often a change in priorities |
| 3.01 | Delisting notice or listing rule problem | Distress signal |
| 2.02 | Reported financial results | Routine, usually filtered out for alerts |
| 9.01 | Financial statements and exhibits | Attached to most filings, never picked as the main event |
Each record also has three flags that make filtering easy in a spreadsheet or CRM:
-
hasLeadershipChangeis true when the filing includes item 5.02. -
hasDealOrAcquisitionis true when it includes 1.01, 2.01 or 5.01. -
hasDistressSignalis true when any item falls in the distress category (bankruptcy, accelerated debt, impairments, delisting notices, non-reliance on earlier financial statements).
primaryEvent picks the most sales-relevant item in the filing, so a filing with items 1.01, 5.02 and 9.01 gets 1.01, Signed a major agreement, as its main event.
The Actor reports only that a leadership change happened. It never opens the filing documents and does not return the names of officers, directors or anyone else. Use filingUrl to read the filing itself. The Actor is independent and is not affiliated with or endorsed by the SEC.
Step 1: Run it once in the Apify Console
Open the Store page (link at the end) and click Start. The example input returns filings from the last 3 days that report a major agreement, a completed acquisition or an officer or director change. A run with no input finishes in a few seconds and returns up to 200 filings.
Then narrow it down:
-
Event types (8-K items) (
itemCodes): only filings with at least one of these items, for example["1.01", "2.01", "5.02"]. -
Skip filings that only report (
excludeItemCodes): for example["2.02", "7.01"]to drop filings that only announce results or investor presentations. -
Industry (SIC codes) (
sicCodes): a code or prefix, such as7372for prepackaged software or28for chemicals and pharma. -
Business address state (
states) and State of incorporation (incorporationStates). -
Companies (tickers or CIKs) (
companies): your named accounts, for exampleAAPLor320193. -
Maximum records (
maxItems): default 200.0means no limit. -
User-Agent sent to SEC (
userAgent): optional. You can put your company name and a contact email here, which the SEC fair access policy asks automated clients to send. The Actor sends its own if you leave it empty.
Step 2: Schedule it daily with onlyNew
- Switch on Only filings not seen in previous runs (
onlyNew). The Actor remembers the accession number (the SEC's unique filing ID) of every filing it returns for your filters, in a key-value store in your Apify account. Later runs skip those, so you are never charged twice for the same filing. - Use a short window with some overlap, such as
daysBack: 3. The date window is not part of the memory, so the overlap catches anything late without repeating it. - Save the input as a task, then create a schedule, for example cron
0 7 * * 1-5with timezoneAmerica/New_York, and add the task.
Changing a filter starts a fresh history.
Step 3: Send alerts to Slack or email without code
In the task's Integrations tab:
- Slack: the Slack integration posts a message to a channel you choose when a run succeeds. You can write your own message template. It works well as a "today's 8-K signals are ready" ping with a link to the run.
- Email: the Gmail integration sends an email after each successful run and can attach the run's dataset.
- One message per filing: use the Apify app in Zapier, Make or n8n. Trigger on a finished task run, fetch the dataset items, then post each item to Slack or create a task in your CRM.
If you would rather control the message yourself, the next two steps do it in code.
Step 4: Python, with a Slack message per signal
Install the client with pip install apify-client, copy your Apify API token from the Integrations section of the Console, and create a Slack incoming webhook for the channel you want.
import json
import os
import urllib.request
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
run_input = {
"daysBack": 3,
"itemCodes": ["1.01", "2.01", "5.01", "5.02"],
"sicCodes": ["7370", "7371", "7372", "7373", "7374", "2834"],
"onlyNew": True,
"maxItems": 500,
}
# Starts the run and waits for it to finish. Caps spend at $2 for this run.
run = client.actor("agents001/form-8k-company-events").call(
run_input=run_input,
max_total_charge_usd=2,
)
if run is None or run.status != "SUCCEEDED":
raise SystemExit("Run failed to start or finish")
lines = []
for f in client.dataset(run.default_dataset_id).iterate_items():
if not (f["hasLeadershipChange"] or f["hasDealOrAcquisition"]):
continue
ticker = f" ({f['ticker']})" if f.get("ticker") else ""
lines.append(
f"*{f['companyName']}*{ticker}, {f.get('businessLocation') or 'US'}: "
f"{f['primaryEvent']} (filed {f['filedDate']}) <{f['filingUrl']}|filing>"
)
if lines:
text = "New 8-K sales triggers:\n" + "\n".join(lines[:40])
req = urllib.request.Request(
SLACK_WEBHOOK_URL,
data=json.dumps({"text": text}).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp:
print(f"Posted {len(lines)} signals to Slack, status {resp.status}")
else:
print("No new signals today")
call() returns None if the run could not be started or followed, so the check matters. The message is capped at 40 lines to keep it readable.
Step 5: The same in JavaScript
With Node.js 18 or later, install the client with npm install apify-client and save this as signals.mjs:
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = {
daysBack: 3,
itemCodes: ['1.01', '2.01', '5.01', '5.02'],
companies: ['ORCL', 'CRM', 'WDAY', 'ADBE'],
onlyNew: true,
};
// Starts the run and waits for it to finish. Caps spend at $1 for this run.
const run = await client
.actor('agents001/form-8k-company-events')
.call(input, { maxTotalChargeUsd: 1 });
if (run.status !== 'SUCCEEDED') {
throw new Error(`Run finished with status ${run.status}`);
}
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const f of items) {
const flags = [
f.hasLeadershipChange && 'leadership change',
f.hasDealOrAcquisition && 'deal or acquisition',
f.hasDistressSignal && 'distress',
].filter(Boolean);
console.log(`${f.filedDate} ${f.companyName} (${f.ticker ?? f.cik}): ${f.eventSummary}`);
console.log(` flags: ${flags.join(', ') || 'none'} ${f.filingUrl}`);
}
Run it with node signals.mjs. Here the companies filter watches a named account list. listItems() returns the dataset items in one call. For very large datasets, pass offset and limit to page through them.
What the output looks like
Here is a real record from a sample run on 23 October 2026, trimmed. It is a filing that reports a completed acquisition alongside a leadership change:
{
"companyName": "Bolt Biotherapeutics, Inc.",
"cik": "0001641281",
"ticker": "BOLT",
"primaryEvent": "Completed an acquisition or sale of assets",
"primaryEventCode": "2.01",
"eventItemCodes": ["1.01", "1.02", "2.01", "3.02", "5.02", "5.03", "7.01", "8.01", "9.01"],
"eventCategories": ["agreement", "acquisition", "financing", "leadership-change", "governance", "investor-communication", "other-event", "exhibits"],
"hasLeadershipChange": true,
"hasDealOrAcquisition": true,
"hasDistressSignal": false,
"industry": "Pharmaceutical Preparations",
"sicCode": "2834",
"stateOfIncorporation": "DE",
"businessLocation": "Redwood City, CA",
"formType": "8-K",
"filedDate": "2026-10-23",
"periodOfReport": "2026-10-23",
"accessionNumber": "0001193125-26-429647",
"filingUrl": "https://www.sec.gov/Archives/edgar/data/1641281/000119312526429647/0001193125-26-429647-index.htm"
}
And a simpler leadership change, where eventSummary gives a readable line:
{
"companyName": "Limitless X Holdings Inc.",
"ticker": "LIMX",
"primaryEvent": "Officer or director change",
"primaryEventCode": "5.02",
"eventSummary": "5.02 Officer or director change; 5.03 Changed articles, bylaws or fiscal year; 9.01 Financial statements and exhibits",
"hasLeadershipChange": true,
"hasDealOrAcquisition": false,
"industry": "Services: Miscellaneous Amusement & Recreation",
"businessLocation": "Beverly Hills, CA",
"filedDate": "2026-10-23",
"periodOfReport": "2026-06-03"
}
Note periodOfReport against filedDate. The period of report is the date of the event, and some companies file months later, so check it before you write "congratulations on the new role".
In that sample of 200 filings, 22 had a leadership change, 24 had a deal or acquisition flag and 4 had a distress signal. The most common items were 9.01, 2.02 and 7.01, which is why excludeItemCodes is worth setting.
What it costs
The Actor uses pay-per-event pricing with platform usage included: $2.00 per 1,000 filings on the Free plan, lower on paid Apify plans (down to $1.40 per 1,000). You only pay for filings saved to your dataset.
All US companies together file roughly 250 to 350 Form 8-Ks on a normal business day. An unfiltered daily feed of 300 filings is $0.60 a day. A feed filtered to deals and leadership changes in a few industries is usually a few dozen filings, well under $0.10 a day. Set a maximum charge per run and the Actor stops cleanly when it is reached. Check the Store page for current pricing.
Limitations
- 8-Ks are filed after the event, usually within four business days, so a large deal may already be in the news.
- Only companies that file with the SEC are covered, which leaves out most private companies.
- Tickers are empty for companies without listed securities. Every record has a CIK, which identifies the company exactly.
- The Actor classifies filings by their item codes. It does not read the filing text, so it cannot tell you who the new executive is or what was acquired. Open
filingUrlfor that. - Amendments (8-K/A) are off by default. Switch on Include amended filings if you want them.
- This is not investment advice. Check the original filing before acting on it.
Top comments (0)