If your congress-trades feature stopped working, you're not alone. House Stock Watcher and Senate Stock Watcher were the default free source for STOCK Act trade data for years, and a lot of projects still point at them. They are no longer usable:
| Source | Status (checked Oct 2026) |
|---|---|
house-stock-watcher-data.s3-us-west-2.amazonaws.com |
403 Forbidden |
senate-stock-watcher-data.s3-us-west-2.amazonaws.com |
403 Forbidden |
housestockwatcher.com, senatestockwatcher.com (incl. /api) |
Domain doesn't resolve |
raw.githubusercontent.com/timothycarambat/senate-stock-watcher-data/... |
Still responds, but last commit was March 2021. Using it gives you 5-year-old data with no error message |
The last one is the dangerous case. I found several projects that use the GitHub mirror as a "fallback". That fallback never fails, so nobody notices the data stopped in 2021.
You can check the others yourself:
curl -I https://house-stock-watcher-data.s3-us-west-2.amazonaws.com/data/all_transactions.json
# HTTP/1.1 403 Forbidden
Below are your options, from free-but-work to paid-but-easy.
Disclosure: I build one of the options below (option 3). I've tried to list the others fairly.
Option 1: Parse the official sources yourself (free)
All of this data comes from two government sites:
-
House: the House Clerk Financial Disclosure site publishes a yearly ZIP with an XML index of every filing (
{year}FD.zip). Periodic Transaction Reports (PTRs) are PDFs at/public_disc/ptr-pdfs/{year}/{doc_id}.pdf. - Senate: Senate eFD has a search UI. You must accept its terms, and it uses a session and CSRF token. Electronic PTRs are HTML tables. Paper filings are scanned images.
The catch is that the House side is PDF parsing. Most PTRs have a text layer, but the layout varies:
- amounts can be split across lines
- some filings list exchanges
- around 14% of recent House PTRs are scanned images with no text at all
A naive regex parser silently drops a lot of rows. Mine dropped almost half of them before I caught it. If you go this route, count filings in vs. rows out, and log the filings that produce zero rows.
Good fit: you want full control and don't mind maintaining a parser.
Option 2: Commercial APIs and sites
Several services sell this data, including Quiver Quantitative, Unusual Whales, Capitol Trades (web UI) and some general market-data APIs that have congress endpoints. They're polished, but check two things before you integrate:
- Pricing and rate limits for the volume you need.
- Commercial-use terms. Federal law (5 U.S.C. §13107(c)) restricts some commercial uses of these disclosure reports, and vendors handle that differently.
Good fit: you need a dashboard or many extra datasets, and the price works for you.
Option 3: Hosted feeds on Apify (pay per row)
I maintain two Apify actors that parse the official House and Senate sources into one clean JSON schema:
What you get:
-
One schema for both chambers, with
member_bioguide_idon each row so you can join to other congress data - Filters: member (nickname-aware), ticker, filing-date window, transaction-date range
-
No silent drops: filings that can't be parsed (scanned or failed) come back as placeholder rows with a
parse_status, instead of disappearing -
Amendment handling:
filing_type,supersedes_filing_id,is_superseded - Pricing: $2 per 1,000 rows (House) and $3 per 1,000 rows (Senate). Apify's free plan includes monthly credit, which covers a typical weekly pull.
Python example:
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("seralifatih/congress-trading-pipeline-1").call(
run_input={"fetchDaysBack": 30, "tickers": ["NVDA", "MSFT"]}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["transaction_date"], row["politician"], row["ticker"],
row["type"], row["amount_min"], row["amount_max"])
Migrating from Stock Watcher fields
If your code expects the old Stock Watcher JSON, the mapping is mostly direct:
| Stock Watcher | This schema | Note |
|---|---|---|
representative / senator
|
politician |
normalized name; raw name in politician_raw
|
transaction_date |
transaction_date |
ISO YYYY-MM-DD
|
disclosure_date |
filing_date |
|
ticker |
ticker |
empty when the filing has none |
asset_description |
asset_name |
|
type (purchase, sale_full, …) |
type |
buy / sell / exchange
|
amount ("$1,001 - $15,000") |
amount_min, amount_max
|
already numeric |
owner |
owner |
self / spouse / joint / child
|
ptr_link |
pdf_url |
House |
Good fit: you want the old "just give me JSON" experience without maintaining a parser.
Option 4: Just need history? Free 2025 snapshot
If you only need a fixed dataset for backtesting or analysis, I published every 2025 House and Senate trade as a free CSV/Parquet file. It has 8,462 rows, 127 members, and the same schema as option 3:
import pandas as pd
df = pd.read_parquet("hf://datasets/seralifatih/us-congress-stock-trades-2025/us_congress_trades_2025.parquet")
One finding from that data: about 12% of 2025 trades were disclosed more than 45 days after the trade, which is past the STOCK Act deadline. If your strategy assumes disclosures arrive on time, check that assumption.
Quick checklist if your feature broke
- Search your code for
stock-watcher,stockwatcher.comandtimothycarambat. - Remove any "fallback" to the GitHub mirror. It hides the outage.
- Pick an option above and update the field mapping.
- Add a freshness check: alert if the newest
filing_dateis more than ~7 days old.
If you know another working source, leave a comment and I'll add it to the list.
Top comments (5)
The GitHub mirror point is the most important line here. A source that 403s gets fixed the same day; a source that keeps answering with 2021 data can sit in production for years. A cheap guard for any of these feeds: assert that the newest transaction date is within N days of today, and fail loudly if it isn't.
Your "count filings in vs rows out" advice applies to every disclosure parser. We parse SEC XBRL filings at scale and the same rule caught most of our silent losses: every filing that produces zero rows goes in a log, and the total gets compared against the index.
For the ~14% scanned House PTRs, do you OCR them, or mark them as missing? For anyone backtesting "follow Congress" strategies, the difference matters: dropping the scanned ones isn't random if some members always file on paper.
Marked as missing, not OCR'd. I tried OCR and froze it. The forms are checkbox grids, and Tesseract kept misreading a printed 2 as a 7 in dates. A guessed date that looks real is worse than a flagged gap, so a scanned filing stays a placeholder with the member, filing date and a link to the PDF.
And you're right that it isn't random. In my 12-month check, two members filed on paper every single month, and one of them accounted for roughly 80% of the estimated rows across the scanned filings I sampled. For a backtest, that means a "follow Congress" strategy silently under-weights those members. The placeholder rows are there so you can at least see which members are affected and decide how to handle them, but the trades themselves are missing.
The freshness guard is a good one. I had a related bug where a date-window input was ignored and runs quietly used the 90-day default, and an "newest transaction within N days" check would have caught it on day one. Adding it. And the log of zero-row filings compared against the index total is exactly what I do now, so it's nice to hear it holds up at SEC scale.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.