ποΈ U.S. House Congress Trade Tracker
Pricing
from $1.40 / 1,000 transaction records
ποΈ U.S. House Congress Trade Tracker
Track every U.S. House member stock trade automatically. Clean, structured data from official disclosures β member name, ticker, trade date, amount. Perfect for investors following congress trades & quant researchers. No PDF parsing needed.
Pricing
from $1.40 / 1,000 transaction records
Rating
5.0
(1)
Developer
Fatih Δ°lhan
Maintained by CommunityActor stats
1
Bookmarked
18
Total users
3
Monthly active users
4 days ago
Last modified
Categories
Share
U.S. House Trading Pipeline
Nancy Pelosi files a $500kβ$1M purchase of Nvidia options. Three days later it's on Reddit. Two weeks later it's on the news.
This pipeline delivers that filing β and every other House PTR β as clean JSON, within 24 hours of the official disclosure. No third-party aggregators. Direct from the Clerk of the House.
Part of a set:
- Senate Trading Pipeline β same target schema, separate fetcher + PDF parser. Run either or both.
- Congress Lobbying Γ Trades Overlap β joins House + Senate trades with federal lobbying filings by member, quarter, and sector.
Who uses this
- Retail traders tracking which Congress members are buying/selling before major legislation β defense stocks before NDAA votes, pharma before drug pricing bills, tech before antitrust hearings
- Developers and analysts building research tools, alerts, or dashboards on top of STOCK Act data
- Journalists and researchers monitoring congressional trading patterns β no account, no paywall, raw government data
- Quiver Quantitative / Capitol Trades users who want the raw feed instead of a third-party UI
Why this instead of Quiver or Capitol Trades? Both aggregate from the same source β the Clerk of the House. This pipeline pulls directly from the official ZIP archive. No middleman, no rate limits, no subscription.
What it produces
One row per individual transaction reported in a House PTR:
{"id": "4d6016b44239f646476ffac6798f21ae3e32c8ed75ea6c5b50a0bbdf9e5d3296","politician": "Mark Alford","transaction_date": "2026-03-16","filing_date": "2026-03-31","ticker": "AMZN","asset_name": "Amazon.com, Inc. - Common Stock","asset_type": "Stock","type": "sell","amount_min": 1001,"amount_max": 15000,"owner": "self","source_id": "house_20034201_0","content_hash": "9e5d3296a3f9c1e2b8d47f60a1c5e93b2d8f7a4c6e0b1d9f3a7c2e5b8d4f6a0c","filing_type": "original","parse_status": "ok","pdf_url": "https://disclosures-clerk.house.gov/public_disc/ptr-pdfs/2026/20034201.pdf","fetchedAt": "2026-03-31T09:12:44.000Z","lastModifiedAt": "2026-03-31T09:12:44.000Z","revisionCount": 0}
| Field | Type | Notes |
|---|---|---|
id | string | SHA-256 of politician|date|asset|amount_min|amount_max|source_id β unique per row, changes if source_id changes |
politician | string | Filer name as it appears on the PTR |
transaction_date | YYYY-MM-DD | null | Trade execution date. null on a scanned_unparsed placeholder row β see parse_status |
filing_date | YYYY-MM-DD | Date the PTR was submitted to the House Clerk |
ticker | string | null | null for bonds, municipals, structured notes β also null on a scanned_unparsed placeholder row |
asset_name | string | null | Full asset description. null on a scanned_unparsed placeholder row |
asset_type | string | null | Stock, Stock Option, Mutual Fund, Corporate Bond, Government Security, etc. null on a scanned_unparsed placeholder row |
type | 'buy' | 'sell' | 'exchange' | null | Purchase β buy; Sale (Full)/Sale (Partial) β sell; Exchange (asset type code [E] β e.g. shares received/surrendered in a merger) β exchange. null on a scanned_unparsed placeholder row |
amount_min | integer | null | Lower bound of reported amount range, USD. null on a scanned_unparsed placeholder row |
amount_max | integer | null | Upper bound. null for unbounded "Over $X" disclosures, and on a scanned_unparsed placeholder row |
owner | 'self' | 'joint' | 'spouse' | 'child' | null | Account owner per STOCK Act categories. null on a scanned_unparsed placeholder row |
source_id | string | Source PTR's DocID + row ordinal (house_<DocID>_<row_index>), or house_<DocID>_scanned for a placeholder row |
content_hash | string | SHA-256 of politician|date|asset|type|amount_min|amount_max|owner (source_id excluded) β see "Duplicate transactions across filings" below |
filing_type | 'original' | 'amendment' | null | Read from the PTR's own per-row "Filing Status: New/Amended" line. null when that line is missing β never guessed. No amendment-number equivalent exists in this source |
parse_status | 'ok' | 'scanned_unparsed' | 'ok' for a normally-parsed row. 'scanned_unparsed' means this filing's PDF has no extractable text layer (scanned/paper PTR, no OCR fallback) β see "Scanned and paper filings" below |
pdf_url | string | The source House PTR PDF this row was parsed from (or, for a scanned_unparsed row, the PDF that couldn't be read) |
fetchedAt | string (ISO 8601 UTC) | When this row was first pulled from source. Immutable β never updated by a later re-fetch of the same, unchanged row |
lastModifiedAt | string (ISO 8601 UTC) | When this row's content last changed. Equal to fetchedAt until a revision is detected |
revisionCount | integer | How many times this source row's content has changed since it was first seen. 0 if never revised |
Scanned and paper filings
Older House PTRs were filed on paper and exist only as scanned images
β the PDF has no text layer, and pdf-parse returns nothing. There is
no OCR fallback. Rather than dropping these filings silently, each one
produces exactly one placeholder row: politician, filing_date,
source_id, and pdf_url are populated, parse_status is
"scanned_unparsed", and every transaction-detail field
(transaction_date, ticker, asset_name, asset_type, type,
amount_min, amount_max, owner) is null. Filter these out with
parse_status = "ok", or use pdf_url to go read the filing yourself.
In a recent 50-filing sample, roughly 12% of filings hit this path.
Duplicate transactions across filings
id is unique per row (it includes source_id), so rows never
collide β but the same real-world trade can still appear under two
different ids if it's reported in more than one source document.
content_hash fingerprints only the trade's real-world content
(source_id excluded), so duplicate copies hash identically.
We never drop or merge rows. Same document (same source_id
prefix) sharing a content_hash is a legitimate separate transaction
β keep both. Different documents sharing one is the same trade
reported more than once β summing both double-counts it.
Fetch timestamps and immutable history
The House Clerk system can revise a PTR after it's first posted, with
nothing in the source flagging that it happened. fetchedAt is set
once, the first time a row is pulled, and never changes after that,
even across a revision. lastModifiedAt moves to the revision's fetch
time when the source republishes a row with different content, and
revisionCount counts how many times that's happened. Rows are never
overwritten in place β a revision lands as a new row that carries
fetchedAt forward from the prior version, so both stay in the
dataset.
To use it: keep a snapshot of a prior pull and diff it against a fresh
one. Where two rows share source_id but differ in content_hash,
lastModifiedAt tells you when the value changed.
Use with Claude, Cursor, or any MCP client
Add this URL as an MCP server to give your AI agent direct access to both actors:
https://mcp.apify.com?tools=seralifatih/congress-trading-pipeline,seralifatih/congress-trading-pipeline-1
Cursor (.cursor/mcp.json):
{"mcpServers": {"congress-trades": {"url": "https://mcp.apify.com?tools=seralifatih/congress-trading-pipeline,seralifatih/congress-trading-pipeline-1"}}}
Apify CLI:
$apify mcp install cursor --tools seralifatih/congress-trading-pipeline,seralifatih/congress-trading-pipeline-1
On first connection you'll be asked to sign in to Apify. Runs are billed to your Apify account at the normal pay-per-result price.
How it works
ZIP fetch XML parse PDF download Text extract Normalizeββββββββββββββββ ββββββββββββββββββ βββββββββββββββββ ββββββββββββββββ βββββββββββββ <YEAR>FD.zip βββΆβ <YEAR>FD.xml βββΆβ /ptr-pdfs/ βββΆβ pdf-parse βββΆβ buy/sell ββ from β β filter β β <YEAR>/ β β + marker- β β + amount ββ disclosures- β β FilingType='P' β β <DocID>.pdf β β anchored β β ranges ββ clerk β β + date window β β (~600ms each) β β regex β β + dates βββββββββββββββββ ββββββββββββββββββ βββββββββββββββββ ββββββββββββββββ ββββββββββββββΌβββββββββββββββββββββ Dedup (SHA-256) ββ + Apify Dataset βββββββββββββββββββββ
1. ZIP fetch. A single HTTPS GET pulls the year-to-date ZIP from https://disclosures-clerk.house.gov/public_disc/financial-pdfs/<YEAR>FD.zip. No proxy or login needed β plain HTTPS.
2. XML index. Inside the ZIP is <YEAR>FD.xml listing every disclosure for the year. Filter to FilingType=P (Periodic Transaction Report) within the configured date window.
3. Per-PTR PDF fetch. Each XML entry has a DocID. Fetch https://disclosures-clerk.house.gov/public_disc/ptr-pdfs/<YEAR>/<DocID>.pdf for each one. Rate-limited to 600ms between requests.
4. Text extraction. pdf-parse reads the PDF and returns text. House PTRs are machine-generated so the text is clean β but the layout has quirks (header null bytes, glued fields, comment-block bleed).
5. Marker-anchored parsing. Each transaction row in the PDF includes a (TICKER) [TYPE] marker. The parser anchors on these markers, walks backward for the asset name, forward for the transaction details, and emits one record per marker.
6. Normalize + dedup + push. Map source codes (P/S/S (partial), SP/DC/JT) to the canonical schema, hash the natural key for dedup, push to the default Apify dataset. A same-source_id row with a changed content_hash is logged as a revision and its revisionCount/lastModifiedAt updated β see "Fetch timestamps and immutable history" above.
Older filings filed on paper produce scanned-image PDFs that pdf-parse can't extract from. There is no OCR fallback, so the parser emits a parse_status: "scanned_unparsed" placeholder row for that filing instead of dropping it β see "Scanned and paper filings" above. Roughly 12% of recent PTRs hit this path. OCR fallback is on the Phase 2 list.
Apify deployment
The actor lives at apify.com/seralifatih/congress-trading-pipeline-1.
To run it via API:
# Trigger a runcurl -X POST "https://api.apify.com/v2/acts/seralifatih~congress-trading-pipeline-1/runs?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{ "fetchDaysBack": 30 }'# Read the datasetcurl "https://api.apify.com/v2/datasets/<dataset-id>/items?token=YOUR_TOKEN&format=json"
Input schema
| Field | Type | Default | Description |
|---|---|---|---|
fetchDaysBack | integer | 90 | Rolling window of PTRs to fetch (1-365) |
fromDate | string (YYYY-MM-DD) | β | Explicit start date. Overrides fetchDaysBack |
toDate | string (YYYY-MM-DD) | today | Explicit end date |
debugPtrLimit | integer | 0 | Diagnostic β fetch only first N PTRs |
debugPdfText | boolean | false | Log first 2KB of any PDF where regex finds 0 rows |
Self-hosting
If you'd rather run it yourself:
git clone https://github.com/seralifatih/congress-trading-pipelinecd congress-trading-pipeline/housenpm installcp .env.example .envnpm run buildnode dist/apify.js # or wire your own runner around runPipeline()
The pipeline's main export is in src/scheduler/pipeline.ts:
import { runPipeline } from './scheduler/pipeline.js';import { SqliteStore } from './store/sqliteStore.js';const stats = await runPipeline(SqliteStore.getInstance(), {fromDate: '2026-01-01',toDate: '2026-04-30',});console.log(stats); // { inserted, skipped, errors }
Storage is pluggable β StoreAdapter interface in src/types/index.ts. The repo ships with a SQLite implementation for local runs and an Apify Dataset implementation for cloud runs. Add Postgres or whatever else by implementing the same interface.
Project layout
src/βββ apify.ts Actor entry point β wires runPipeline + ApifyStoreβββ fetcher/β βββ houseFetcher.ts ZIP download + XML index + per-PDF fetchβββ parser/β βββ housePdfParser.ts Marker-anchored regex extractorβββ transformer/β βββ normalize.ts Source codes β canonical schemaβββ store/β βββ sqliteStore.ts Local SQLite via better-sqlite3β βββ apifyStore.ts Apify Dataset via Apify SDKβββ scheduler/β βββ pipeline.ts Fetch β parse β normalize β dedup β saveβββ utils/β βββ config.ts Zod-validated env varsβ βββ dedup.ts SHA-256 ID generationβ βββ retry.ts Exponential backoff with jitterβ βββ logger.ts JSON-lines structured loggerβββ types/βββ index.ts RawTransaction, Transaction, StoreAdapter, schemas
Data source and permitted use
Data is sourced from public STOCK Act Periodic Transaction Reports published by the U.S. House Clerk and the U.S. Senate eFD system, and is provided for informational and research purposes.
Users are responsible for ensuring their use complies with 5 U.S.C. Β§13107(c), which prohibits obtaining or using these reports for any unlawful purpose; any commercial purpose other than by news and communications media for dissemination to the general public; determining an individual's credit rating; or soliciting money for political, charitable, or other purposes.
Not investment advice. Disclosures are filed up to 45 days after a trade and report amount ranges, not exact values.
This actor's source is the Clerk of the U.S. House β Financial Disclosure Reports, published under the STOCK Act of 2012. The Clerk publishes a fresh ZIP daily containing every disclosure filed that year. This pipeline does not scrape third-party aggregators. It pulls only from the official source.
Phase 2
- OCR fallback for scanned PDFs (older paper filings)
- Ticker enrichment for bond/muni rows where the source omits the ticker
Cross-chamber merge actorβ shipped as Congress Lobbying Γ Trades Overlap, which consumes both Senate + House datasets and joins them with LDA lobbying filings
License
The code is MIT-licensed. That license covers the code only; use of the data is governed by "Data source and permitted use" above.