Early Vote & Mail Ballot Tracker — County Turnout & Results avatar

Early Vote & Mail Ballot Tracker — County Turnout & Results

Pricing

from $4.25 / 1,000 county data row (standard)s

Go to Apify Store
Early Vote & Mail Ballot Tracker — County Turnout & Results

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

Scott Helvick

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 hours ago

Last modified

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 TOTAL row 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 API behind 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's COVERAGE key-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

ApproachCounty-levelFresh daily in-seasonOne schema across statesHonest coverage labels
Individual state portals / filesYesVariesNo (per-state formats)No
Static county election datasetsYesNo (historic only)Within one datasetN/A
National exit-poll / topline feedsNo (state/national)SometimesNoNo
Early Vote & Mail Ballot TrackerYes (FIPS)YesYesYes

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

FieldTypeRequiredDefaultDescription
datasetsarrayall threeWhich datasets to sweep: any of early_vote, registration, results. Empty = all three. Each is billed only for the county rows it returns.
statesarrayall coveredTwo-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.
dateFromstringISO YYYY-MM-DD, inclusive. Bounds early_vote snapshot dates and registration as-of dates. Ignored for results.
dateTostringISO YYYY-MM-DD, inclusive upper bound for early_vote / registration. Ignored for results.
countiesarrayall5-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.
resultsYearsarrayallYear filter for results, e.g. ["2020","2024"] (numeric strings are coerced). Applies only to results.
electionIdstringOptional: 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.
maxRowsinteger200000Safety 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): expose shelvick/early-vote-tracker as 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 ApifyClient
    client = 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-items for a synchronous sweep, or the async /runs endpoint 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). Only ok bills.
  • Never a false empty. If the backing service is unreachable or a slice errors, that slice comes back as a failed row with an error string and the run is marked failed — the sweep never silently returns nothing. Zero-row live slices are explained in the COVERAGE manifest, 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. maxRows and 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