Early Vote & Mail Ballot Tracker — County Turnout & Results
Pricing
from $4.25 / 1,000 county data row (standard)s
Early Vote & Mail Ballot Tracker — County Turnout & Results
County-level US election data in one sweep: daily early-vote & mail-ballot returns by county, party, method and day, voter-registration statistics with history, and county results history 2000-2024. Honest per-state coverage labels; built for forecasting and analysis agents.
Pricing
from $4.25 / 1,000 county data row (standard)s
Rating
0.0
(0)
Developer
Scott Helvick
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 hours ago
Last modified
Categories
Share
Early Vote & Mail Ballot Tracker
Election-forecasting and political-analysis agents need county-level early-vote and mail-ballot numbers that no training corpus can contain — and today those numbers live in fifty different state file formats. This Actor is an early vote tracker that returns daily mail ballot returns by county, voter registration statistics by county and party, and county election results 2000–2024 as one normalized sweep, with honest per-state coverage labels.
What this does
- Early vote & mail ballots (daily). County × party × method returns for covered states — daily absentee ballot counts, mail ballots requested / returned / accepted, and early in-person votes, one cumulative snapshot per day so you get the returns time series, not just today's number. Current-cycle files light up as each state publishes them; prior-cycle archives are there to baseline against.
- Voter registration by county and party. County-level registration
statistics — registered voters per county × party — as a current snapshot plus
the monthly/weekly history where the state publishes it. In no-party-registration
states only a
TOTALrow exists and the record says party breakdown is omitted by law, not missing. - County election results history. The county-level presidential results
panel 2000–2024 plus Senate 2022, keyed by 5-digit FIPS — a
county-level presidential results APIbehind the same schema as the fresh data. Alaska's house-district reporting is kept honest (keyed to district, not force-mapped to a county). - One normalized schema across every source. Every state source is a different
parser; the output is one unified row shape discriminated by
dataset, so an agent sums party keys and joins FIPS the same way in every state. - Honest coverage, never a silent empty. Every requested state × dataset slice
that is not live comes back as a labeled coverage row (
not_covered,in_season_pending,gateway_deferred,archive_only,failed), and the run'sCOVERAGEkey-value record always carries per-slice delivered counts and reasons. - Use cases: feed a daily early-vote model with fresh county × party × method returns during the voting window; baseline this cycle's mail-ballot returns against the prior cycle's same-day archive; pull the full county presidential panel 2000–2024 to train or validate a county-level model; track registration drift by county and party between snapshots; assemble a single fresh-plus-historic county election dataset without stitching fifty state portals together.
Why daily freshness matters
The demand this is built for is a pipeline that re-runs daily during the Sept–Nov voting window. Daily early-vote counts are post-training-cutoff by construction — today's ballots-returned number cannot be in any model's training data, so an agent that needs it must call a live tool. That is the whole thesis: freshness a static corpus can't match.
The expensive, brittle part is that every state ships its numbers differently — one state's aggregate CSV, another's tab-delimited text, a third's open-data query, a fourth's spreadsheet. Assembling county × party × method returns across states, day after day, and keeping the schema stable while the underlying files churn mid-season is exactly the work you don't want inside your model. This Actor is that assembly layer: the slow, walled, format-churning sources are refreshed behind an index, and your call is a fast sweep over normalized rows.
And it's honest about the edges. A source that only publishes during an active election, a state that's behind a bot wall, a results panel that doesn't exist at the county level — each comes back as a labeled row, so your pipeline can branch on coverage instead of guessing whether an empty result means "no data" or "no ballots." A smaller map that tells the truth beats a bigger one that returns silent zeros.
How it compares to raw sources and static datasets
| Approach | County-level | Fresh daily in-season | One schema across states | Honest coverage labels |
|---|---|---|---|---|
| Individual state portals / files | Yes | Varies | No (per-state formats) | No |
| Static county election datasets | Yes | No (historic only) | Within one dataset | N/A |
| National exit-poll / topline feeds | No (state/national) | Sometimes | No | No |
| Early Vote & Mail Ballot Tracker | Yes (FIPS) | Yes | Yes | Yes |
The bet: multi-jurisdiction assembly behind one schema, plus daily freshness and honest coverage, is worth more to a forecasting workflow than any single state portal scraped in isolation or any snapshot dataset frozen at last cycle.
Input
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
datasets | array | all three | Which datasets to sweep: any of early_vote, registration, results. Empty = all three. Each is billed only for the county rows it returns. | |
states | array | all covered | Two-letter postal codes, e.g. ["NC","FL","PA"]. Empty = every covered state for each requested dataset. A requested-but-uncovered state returns a labeled coverage row, never a silent empty. | |
dateFrom | string | — | ISO YYYY-MM-DD, inclusive. Bounds early_vote snapshot dates and registration as-of dates. Ignored for results. | |
dateTo | string | — | ISO YYYY-MM-DD, inclusive upper bound for early_vote / registration. Ignored for results. | |
counties | array | all | 5-digit FIPS filter, e.g. ["37183"] (Wake County, NC). Empty = all counties. FIPS is the county key across every dataset; a single-county filter turns the sweep into a targeted lookup. | |
resultsYears | array | all | Year filter for results, e.g. ["2020","2024"] (numeric strings are coerced). Applies only to results. | |
electionId | string | — | Optional: pin early_vote to one election by election-day date, e.g. "2024-11-05". Empty = every election whose daily snapshots fall in the date window. | |
maxRows | integer | 200000 | Safety cap on total county data rows returned and billed per run. Coverage rows are free and don't count. The per-run max-charge cap is the hard budget stop. |
Output
One dataset item per row — a unified superset discriminated by dataset;
dataset-specific fields are populated for that shape and null otherwise.
An early_vote row (daily mail-ballot returns, status: "ok"):
{"dataset": "early_vote","state": "NC","status": "ok","county_fips": "37183","county": "Wake","party": "DEM","party_detail": null,"election_id": "2024-11-05","election_name": "2024 General Election","date": "2024-10-28","method": "mail","ballots_returned": 12987,"ballots_accepted": 12750,"in_person_votes": null,"source": "State Board of Elections (county absentee counts)","as_of": "2024-10-28 11:00AM","field_notes": []}
A registration row (registered per county × party):
{"dataset": "registration", "state": "FL", "status": "ok","county_fips": "12086", "county": "Miami-Dade", "party": "REP","snapshot_date": "2026-07-01", "registered": 462180,"source": "State Division of Elections (party affiliation by county)", "field_notes": []}
A results row (county presidential history):
{"dataset": "results", "state": "NC", "status": "ok","county_fips": "37183", "county": "Wake", "year": 2024,"office": "US PRESIDENT", "candidate": "KAMALA D HARRIS","votes": 340199, "total_votes": 621988, "field_notes": []}
A coverage row (requested slice not live — free, never billed):
{"dataset": "results", "state": "TX", "status": "gateway_deferred","coverage_note": "This state's source is not integrated yet for this dataset.","field_notes": []}
Key fields: status (row outcome — only ok bills), party (normalized
DEM/REP/UNA/OTH/TOTAL key, safe to sum), party_detail (the source's own party
code verbatim where it emits one), and field_notes (why a field is structurally
absent — e.g. a count column a state doesn't publish, or party omitted by law). Each
run also writes a COVERAGE key-value record listing every requested slice with its
delivered row count and reason — authoritative per run, and coverage expands over
time.
Example
Sweep this cycle's early-vote returns plus the county presidential history for three states:
curl -X POST "https://api.apify.com/v2/acts/shelvick~early-vote-tracker/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"datasets":["early_vote","results"],"states":["NC","FL","PA"],"dateFrom":"2026-09-01","dateTo":"2026-11-03","resultsYears":["2020","2024"]}'
Returns the daily early-vote rows in the date window plus the 2020 and 2024 county presidential rows for each state, with a labeled coverage row for anything requested that isn't live yet.
Calling from an AI agent
Agents are the primary customer. Three ways to call it:
-
Apify MCP server (
mcp.apify.com): exposeshelvick/early-vote-trackeras a tool; the input schema and per-field descriptions are advertised to the model, so an agent can construct a valid sweep from the tool description alone. Pay per call via x402 USDC on Base or Skyfire managed tokens. -
Apify SDK (Python):
from apify_client import ApifyClientclient = ApifyClient("YOUR_TOKEN")run = client.actor("shelvick/early-vote-tracker").call(run_input={"datasets": ["early_vote"], "states": ["NC", "FL", "PA"],"dateFrom": "2026-09-01", "dateTo": "2026-11-03"})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["state"], row["county"], row["party"], row["date"], row.get("ballots_returned")) -
REST:
POST /v2/acts/shelvick~early-vote-tracker/run-sync-get-dataset-itemsfor a synchronous sweep, or the async/runsendpoint plus polling for a large national pull.
Pricing
Pay-per-event, billed only on success. Each county data row is charged once, after it is pushed to the dataset (push-then-charge), metered per run. The rate depends on what kind of row it is:
- Live-tracking rows (early-vote and registration) bill at a standard per-row rate, and a lower volume rate applies automatically to rows beyond a per-run threshold — large daily sweeps get cheaper per row, by design.
- Results-history rows bill at a flat rate lower than live-tracking rows, so pulling the full historical panel stays inexpensive.
Coverage rows (not_covered, in_season_pending, gateway_deferred,
archive_only, failed) are returned but never charged, and a run stopped by
your budget cap bills only the rows actually delivered — every charge is clamped to
your remaining budget in code, so a broad national sweep can't produce a runaway
bill. A single platform run-start fee applies per run.
See the Pricing tab on this Store page for the current per-row rates, the volume threshold, and any active subscriber discounts.
Behavior
- Row statuses:
ok(a billed county data row);not_covered(state/dataset not in scope yet);in_season_pending(source published only during an active election — e.g. some early-vote files off-season);gateway_deferred(a bot-walled source not yet integrated);archive_only(historical only, not currently refreshing);failed(the backing data could not be reached for that slice). Onlyokbills. - Never a false empty. If the backing service is unreachable or a slice errors,
that slice comes back as a
failedrow with an error string and the run is marked failed — the sweep never silently returns nothing. Zero-row live slices are explained in theCOVERAGEmanifest, and an empty slice inside an active election window raises a diagnostic rather than passing quietly. - Run-level failures (rare): input validation only — states must be 2-letter
codes, counties 5-digit FIPS, dates ISO
YYYY-MM-DD. - Latency: a single-county or single-state sweep returns in a few seconds; a
broad national sweep (tens of thousands of rows) is paged and pushed
incrementally and scales with row count.
maxRowsand the per-run max-charge cap bound the largest pulls.
FAQ
Do I get today's ballot counts, or last week's? The latest snapshot the source has published, per state. In-season, states that publish daily are refreshed daily; each day's cumulative snapshot is retained, so a date range gives you the returns time series, not just the newest figure.
What does a null party or count column mean? It's structural, not a gap. In
no-party-registration states party is TOTAL-only and omitted by law; a count
column a state doesn't publish is left null rather than duplicated from another
column. The row's field_notes says which and why.
Is the results data official? It's the standard county-level returns panel — president 2000–2024 and Senate 2022 — normalized to FIPS. County-level Governor and House panels don't exist as a multi-cycle county series, and the coverage record says so rather than inventing them.
How do I keep a national sweep from over-charging? Set maxRows and the run's
max-charge cap. Charges are clamped to your remaining budget, and dropped rows are
free and explained in the COVERAGE record.
Which states are covered? Read the run's COVERAGE record — it's authoritative
per run. Early-vote coverage centers on the states that publish clean daily county
files and expands as more come online; registration covers several
partisan-registration states; results covers all 50 states for the county
presidential panel.
What this doesn't do
- No individual voter data. Aggregate county rows only — county × party × method counts, never a voter file, never a name, address, or ballot-level record. This is a deliberate design stance, not a coverage gap: where a source is individual-level, aggregation happens before anything is stored, and raw individual records are never retained or served.
- No forecasting or modeling. Raw normalized returns and the archived baseline series — the projection, turnout model, or seat forecast is yours to build on top.
- No real-time streaming or monitoring. A point-in-time sweep of the latest ingested data, not a live feed or a change-watcher.
- No individual candidate-finance, precinct-level, or exit-poll data. County is the finest grain; precinct and sub-county breakdowns are out of scope.
- Not a 50-state guarantee for the fresh datasets. Early-vote and registration coverage is honest and expanding; uncovered slices return labeled coverage rows.
For a turnout model or seat projection, build it on top of these rows — this Actor is the fresh-data primitive, not the model. For individual voter registration records or a voter file, this Actor deliberately does not serve them; use an appropriately-authorized voter-data provider. For precinct-level or ballot-level returns, use a precinct-returns source — county is the finest grain here.
Design notes: www.scotthelvick.com/tools/early-vote-tracker