# SEC EDGAR Filing Watch (Form 4, 8-K, 10-K, 13D…) (`changefeeds/sec-edgar-filing-watch`) Actor

Track SEC companies run over run: which filings are new since the last run of the same list (Form 4, 8-K, 10-K, 10-Q, 13D/G, S-1 and more), straight from the official, public EDGAR submissions feed. No API key.

- **URL**: https://apify.com/changefeeds/sec-edgar-filing-watch.md
- **Developed by:** [Changefeeds Tools](https://apify.com/changefeeds) (community)
- **Categories:** Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SEC EDGAR Filing Watch (Form 4, 8-K, 10-K, 13D…)

Give it a list of tickers or CIKs. Each run returns their SEC filings
**compared with the previous run of the same list**: on the first run every
matched filing is a `baseline` row; after that you only see filings that are
new since the last run. Form 4 insider trades, 8-Ks with their item codes,
10-K/10-Q, 13D/G, S-1 — whatever you pick.

Schedule it daily or weekly and the dataset becomes a change feed for
insider-trade tracking, disclosure monitoring, or digest building — using only
the official, public EDGAR submissions feed (`data.sec.gov`). No API key, no
login.

### What you get

One `company` row per ticker (`type: "company"`):

- resolved CIK and company name (from EDGAR's own ticker file and feed)
- `is_baseline` (first run of the list), `previous_checked_at`
- `filings_seen` (filings of your form types in EDGAR's recent feed),
  `new_filings` (found for delivery this run; fewer rows arrive if the run's
  budget runs out) and `filings_pending` (found but beyond
  `maxFilingsPerCompany`; delivered by the next runs)
- `status`: `ok`, `not_found` (ticker not in EDGAR's ticker file, or no
  submissions feed), `error` (a request failed), `invalid` (the input entry
  is not a ticker or CIK) or `duplicate` (another share class of a company
  already in the list, e.g. `BRK-A` next to `BRK-B`: checked and charged once)

One `filing` row per matched filing (`type: "filing"`), with a `change_type`:

- `new` — filed since the last check and not delivered before
- `baseline` — history: the first run's filings, and a filing delivered late
  that was accepted more than a day before the previous check

Each row carries `form`, `filing_date`, `report_date`, `accepted_at`,
`accession_number`, `items` (8-K item codes, e.g. `2.02,9.01`),
`description`, and a direct `url` to the primary document on sec.gov.

The key-value store record `OUTPUT` holds a per-company summary plus totals
(`filings_returned`, `baseline_filings`, `new_filings`, `first_run`,
`charged_events`, `charge_limit_reached`, `dataset_id`, `webhook_delivered`).
If you set `webhookUrl`, the summary is POSTed there as JSON when the run ends,
together with the run's `new` filing rows (`new_filing_rows`, up to 500; the
full set is always in the dataset, `dataset_id`). Failed deliveries are retried
3 times (network errors, 429 and 5xx).

### Input

| Field | Default | Notes |
|---|---|---|
| `tickers` | required | Stock tickers (`AAPL`, `BRK.B`) or 10-digit CIKs (`0000320193`). Tickers are resolved through EDGAR's official ticker-to-CIK file; CIKs are used directly. Up to 1,000 per run. Invalid entries become a free error row; the run continues. |
| `formTypes` | `["4","8-K","10-K","10-Q","SC 13D","SC 13G","S-1"]` | Only return these EDGAR form types (case-insensitive). A form includes its amendments (`4` also returns `4/A`; ask for `8-K/A` alone to get only amendments). `SC 13D` and `SCHEDULE 13D` (EDGAR's name since December 2024) are the same, likewise 13G. Leave empty to watch all form types. |
| `maxFilingsPerCompany` | 10 | 1 to 1,000. How many of the most recent matched filings to examine per company. EDGAR's recent-feed window holds roughly 1,000 filings; older ones are not visible to this actor. |
| `snapshotKey` | derived | Which saved state to compare against. By default it is derived from the sorted ticker list, so the same list always compares with itself. Set it explicitly to keep history when you edit the list. Distinct explicit keys always get distinct storage records (the key is sanitised and suffixed with a hash of the full original), so `watch/a` and `watch?a` never share history. |
| `webhookUrl` | none | Receives the run summary and the new filing rows as a JSON POST (Slack/Zapier/Make/your own endpoint). |
| `contactEmail` | operator's | Included in the User-Agent sent to SEC.gov, as their fair-access policy asks. |

```json
{
  "tickers": ["AAPL", "TSLA"],
  "formTypes": ["4", "8-K"],
  "maxFilingsPerCompany": 10
}
```

If a run finds nothing new, the dataset holds one `no_changes` row (not charged), so a quiet run is never mistaken for a broken one.

### Sample output

```json
{
  "type": "company",
  "ticker": "AAPL",
  "status": "ok",
  "cik": "0000320193",
  "name": "Apple Inc.",
  "is_baseline": false,
  "previous_checked_at": "2026-09-27T12:00:00.000Z",
  "filings_seen": 3,
  "new_filings": 1,
  "checked_at": "2026-09-28T12:00:00.000Z"
}
```

```json
{
  "type": "filing",
  "ticker": "AAPL",
  "cik": "0000320193",
  "company": "Apple Inc.",
  "change_type": "new",
  "form": "8-K",
  "filing_date": "2026-09-25",
  "report_date": "2026-09-24",
  "accepted_at": "2026-09-25T22:10:00.000Z",
  "accession_number": "0000320193-26-000077",
  "items": "2.02,9.01",
  "description": "Current report",
  "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000077/exhibit991.htm",
  "checked_at": "2026-09-28T12:00:00.000Z"
}
```

### Pricing

Pay per event, nothing else:

- **$0.002 per company checked** (a company whose filings feed was fetched and
  compared). Unknown, invalid or failed companies are not charged.
- **$0.01 per filing returned** with a change: `baseline` or `new`. Companies
  that have gained nothing since the last run cost only the company check.

Worked examples:

- **Starting a watchlist** of 25 companies with the default 10 recent filings
  each: 25 × $0.002 + 250 × $0.01 = **$2.55**, once. Set
  `maxFilingsPerCompany` to 1 to start for $0.30.
- **A daily run** over those 25 companies: $0.05 in checks plus $0.01 per new
  filing. With only 8-Ks and 10-K/10-Qs, a typical large company files a few
  per month, so roughly **$1.50–$3 a month**; with Form 4 (insider trades)
  included, busy issuers file several a week and it can be 2–4× that.
- **100 companies daily**, 8-K only: $0.20 a day in checks, about **$6 a
  month**, plus $0.01 per 8-K.

If you set a maximum total charge for the run, the actor returns only as many
rows as fit, saves state for what it returned, stops, and says so in `OUTPUT`
(`stopped_reason: "max_total_charge_reached"`). It never charges for a row it
did not deliver. (If a run is killed between delivering a company's rows and
saving its state, e.g. by a timeout or an abort, the next run reports that
company's filings again.)

### How the change feed works

- State lives in the named key-value store `secwatch-snapshots`, one record per
  company (CIK) and snapshot key. The record holds the company name and the
  accession numbers of every filing already delivered (the newest 5,000 are
  kept). The storage key for an explicit snapshot key is the sanitised original
  (max 60 chars) plus a 16-hex-digit hash of the full original, keeping every
  key within Apify's 256-character, `[a-zA-Z0-9!\-_.'()]` limit and
  collision-free.
- The first run of a snapshot key is the baseline: it reads the company's
  recent filings, filters them by form type, takes the newest
  `maxFilingsPerCompany`, and every one is a `baseline` row. The oldest filing
  in that window (its acceptance time) becomes the watch's floor: older history is never
  billed later, even if you widen `formTypes`.
- Later runs take every filing on or after the floor whose accession number
  has not been delivered, newest first, up to `maxFilingsPerCompany` per run;
  the rest are counted in `filings_pending` and delivered by the next runs, so
  a burst of filings is never hidden behind the cap. EDGAR filings are
  append-only, so there are no `removed` changes.
- The first run charges the most; steady-state runs charge only what changed.
- If a run is cut off by the charge limit mid-list, only the rows the user
  actually received are remembered; the cut filings come back on the next run.
- Snapshot saves re-read the stored record and keep the union of accession
  numbers, and a failed re-read is retried rather than overwriting from a stale
  view. Overlapping runs are normally refused (see below); in the rare case two
  start together, a filing can be reported twice.

### Limits, stated plainly

- **Only the recent-feed window.** The actor reads `filings.recent` of each
  company's submissions feed, which holds roughly the latest 1,000 filings per
  company (Form 4-heavy companies roll through that fast). `maxFilingsPerCompany`
  cannot reach further than that window. Historical filings are never backfilled.
- **EDGAR update cadence applies.** EDGAR refreshes its feeds on its own
  schedule; a filing accepted minutes ago may not appear until EDGAR publishes
  it.
- **Ticker resolution depends on EDGAR's ticker file.** Some issuers are
  missing from it or listed under several tickers; a missing ticker comes back
  as `not_found` (not an error, not charged). For those, pass the 10-digit CIK
  directly.
- **Amendments are separate rows.** Filings are identified by accession
  number; an amended filing (`4/A`, `10-K/A`) is its own accession and its own
  row. The actor does not diff the contents of filings.
- **Metadata and links, not filing text.** Rows carry EDGAR's index metadata
  and a link to the document on sec.gov; the actor does not download or
  republish filing documents.
- **`new` uses a day of slack.** EDGAR's acceptance timestamps are Eastern time
  labelled as UTC, so a never-delivered filing counts as `new` unless it was
  accepted more than a day before the previous check.
- **`report_date` and `accepted_at` can be empty** for some form types; those
  fields are then `null` rather than guessed.
- **It stays polite:** at most 4 EDGAR requests per second (their fair-access
  limit is 10), a declared `Changefeeds Tools SEC Filing Watch <contact>`
  User-Agent, HTTP 429 honoured (Retry-After, capped at 60 s, max 3 retries; a
  longer Retry-After stops all further EDGAR requests for the rest of the run,
  and the remaining companies get free `error` rows), 20 s timeouts. The pace
  is per run: several runs at the same moment add up. Two
  companies are fetched at a time; each company costs 1–2 requests
  (ticker-file lookup is one request per run, cached).
- **One run at a time per snapshot key.** If a scheduled run starts while the
  previous one with the same `snapshotKey` is still going, the new run fails
  immediately, before fetching or charging anything. A run that crashed without
  cleaning up blocks its key for at most 30 minutes. (The lock is best effort:
  two runs started within the same couple of seconds can, rarely, both
  proceed; their saved history is merged, not overwritten.)
- **If saved history can't be read, the company is skipped**, with a free
  `error` row, rather than re-sent as a paid baseline. If it can't be *saved*
  after 3 attempts, the run is marked failed and names the companies, because
  the next run would report those filings again.

### Local development

```bash
pnpm --filter @mmnm/secwatch test        # unit tests, no network
pnpm --filter @mmnm/secwatch build
```

`node dist/main.js` runs the actor locally with Apify's local storage
(`CRAWLEE_STORAGE_DIR`); `ACTOR_TEST_PAY_PER_EVENT=true
ACTOR_MAX_TOTAL_CHARGE_USD=1` exercises the charging path with the SDK's $1
local test price.

# Actor input Schema

## `tickers` (type: `array`):

Companies to watch: stock tickers (AAPL, BRK.B) or 10-digit CIKs (0000320193). Tickers are resolved through EDGAR's official ticker-to-CIK file; unknown tickers are reported as not\_found. Up to 1,000 per run.

## `formTypes` (type: `array`):

Only return these EDGAR form types, e.g. 4, 8-K, 10-K, 10-Q, SC 13D, SC 13G, S-1. Leave empty to watch all form types. Matching is case-insensitive.

## `maxFilingsPerCompany` (type: `integer`):

How many of the most recent filings (of the selected form types) to examine per company (1 to 1,000). EDGAR's recent-feed window holds roughly 1,000 filings; older filings are not visible to this actor.

## `snapshotKey` (type: `string`):

Name of the saved state used to compute changes between runs. Leave empty to derive it from the sorted ticker list, so the same list always compares against its own last run. Set it explicitly to keep history when you edit the list.

## `webhookUrl` (type: `string`):

Optional. When the run finishes, the OUTPUT summary is POSTed here as JSON.

## `contactEmail` (type: `string`):

Optional. Included in the User-Agent sent to SEC.gov, as their fair-access policy asks. Defaults to the operator's address.

## Actor input object example

```json
{
  "tickers": [
    "AAPL",
    "TSLA"
  ],
  "formTypes": [
    "4",
    "8-K"
  ],
  "maxFilingsPerCompany": 10
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "tickers": [
        "AAPL",
        "TSLA"
    ],
    "formTypes": [
        "4",
        "8-K"
    ],
    "maxFilingsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("changefeeds/sec-edgar-filing-watch").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "tickers": [
        "AAPL",
        "TSLA",
    ],
    "formTypes": [
        "4",
        "8-K",
    ],
    "maxFilingsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("changefeeds/sec-edgar-filing-watch").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "tickers": [
    "AAPL",
    "TSLA"
  ],
  "formTypes": [
    "4",
    "8-K"
  ],
  "maxFilingsPerCompany": 10
}' |
apify call changefeeds/sec-edgar-filing-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,changefeeds/sec-edgar-filing-watch"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ijKTTTrhz35hrxMQb/builds/BHD2LnfIoD55KixWj/openapi.json
