Skip to main content

API Documentation

Free, public API for U.S. electoral data. CORS enabled for all origins. No authentication required.

Base URL: https://decisionlabs.aiFormat: JSONCORS: *Version: v1Spec: OpenAPI 3.1

Versioning & stability

This API is v1. Breaking changes (removed fields, renamed parameters, changed response shapes) are announced on this page at least 30 days before they ship. Additive changes — new endpoints, new optional parameters, new response fields — may ship at any time without notice, so parse responses tolerantly. A machine-readable OpenAPI 3.1 spec describes the full public surface.

Rate limits: limits are currently best-effort, per-server-instance approximations (counters are not shared across serverless instances), so the effective ceiling you observe may be higher than documented. Treat documented limits as the contract: general endpoints ~100 req/min, bulk poll export 30 req/min per IP. A 429 means back off.

GET/api/elections/:year

All state results for a presidential election year

Parameters

yearpathElection year (1976-2024, divisible by 4)

Example

GET /api/elections/2024

Response

{ year, national: { winner, ev, popular_vote, ... }, states: [...] }
GET/api/elections/:year/:state

Detailed results for one state in one election

Parameters

yearpathElection year
statepathState abbreviation (e.g., PA, CA)

Example

GET /api/elections/2024/PA

Response

{ year, state: { abbr, name, ev_count, margin, candidates, ... } }
GET/api/states/:abbr/history

All presidential results for a state since 1976

Parameters

abbrpathState abbreviation

Example

GET /api/states/PA/history

Response

{ abbr, name, region, ev_history, elections: [...] }
GET/api/polls

Recent polls with optional filtering

Parameters

statequeryFilter by state abbreviation or "national"
daysqueryOnly polls from last N days
min_ratingqueryMinimum pollster rating (A+ through C-)
pollsterqueryFilter by pollster name (partial match)

Example

GET /api/polls?state=PA&days=30

Response

{ polls: [...], meta: { count, filters } }
GET/api/polls/average

Computed polling average with adjustable parameters

Parameters

statequeryState abbreviation or "national"
half_lifequeryRecency half-life in days (default: 14)
min_ratingqueryMinimum pollster rating (default: C-)

Example

GET /api/polls/average?state=PA&half_life=21

Response

{ average, methodology, polls_included: [...] }
GET/api/ratings/:year

Third-party race ratings for the 2026 cycle (Cook, Sabato, Inside Elections). Values are null until verified by automated sourcing.

Parameters

yearpathElection cycle year (2026)

Example

GET /api/ratings/2026

Response

{ year, source_status, last_verified, raters, ratings: [{ raceId, abbr, state, office, cook, sabato, inside_elections }] }
GET/api/swing

Swing analysis between two election years

Parameters

from_yearqueryStarting year (default: 2020)
to_yearqueryEnding year (default: 2024)

Example

GET /api/swing?from_year=2016&to_year=2020

Response

{ from_year, to_year, national_swing, states: [...] }
GET/api/export

Bulk export as CSV or JSON

Parameters

yearqueryElection year (required)
statequeryFilter to one state
formatquery"json" (default) or "csv"

Example

GET /api/export?year=2024&format=csv

Response

CSV file download or JSON array
GET/api/export/polls

Bulk export of ALL 2026 race polls. JSON returns full poll objects with per-candidate results keyed by candidate slug; CSV is tidy format (one row per poll-candidate pair). Cached at the edge for 1 hour; rate limited to 30 req/min per IP.

Parameters

formatquery"json" (default) or "csv"
racequeryFilter to one race id, e.g. ca-gov-2026

Example

GET /api/export/polls?race=ca-gov-2026&format=csv

Response

{ meta: { count, races, race, generatedAt, license }, polls: [{ raceId, date, pollster, grade, sampleSize, population, marginOfError, partisan, results: { candidateSlug: pct }, sourceUrl }] }

Quick start: bulk polls export

All 2026 polls as JSON

curl -s "https://decisionlabs.ai/api/export/polls"

One race as CSV (saved to file)

curl -sL -o ca-gov-2026-polls.csv "https://decisionlabs.ai/api/export/polls?race=ca-gov-2026&format=csv"

Embeds

Every 2026 race has a compact, server-rendered widget at /embed/race/{raceId} showing the race title, our rating, the current polling average per candidate, and a freshness date. It is designed for a ~360–480px column, follows the visitor's light/dark preference, and sets no cookies and runs no trackers.

Copy-paste snippet

<iframe
  src="https://decisionlabs.ai/embed/race/ca-gov-2026"
  width="400"
  height="320"
  loading="lazy"
  title="California Governor 2026 — Decision Labs polling average"
></iframe>

Valid race ids: any raceId listed in meta.races of the bulk export endpoint, GET /api/export/polls (e.g. ca-gov-2026, nc-sen-2026). Unknown ids return 404. Races without public polling render the rating with an honest “no public polling yet” note.

Attribution: embeds display the data license's required attribution (see below — CC BY 4.0, credited to “Decision Labs (decisionlabs.ai)”) in a built-in footer linking to the race page and methodology. Please don't crop or obscure that footer.

Data license

Poll and election data is aggregated from public sources (pollster releases, MEDSL, and other freely licensed datasets). The Decision Labs compilation — including computed averages, grades mapping, and export formats — is released under CC BY 4.0: use it freely with attribution to “Decision Labs (decisionlabs.ai)”. The platform codebase is MIT-licensed. Underlying source data remains subject to its original licenses.

Changelog

2026-06-10

  • Added GET /api/export/polls — bulk export of all 2026 race polls (JSON + CSV, optional ?race= filter).
  • Published machine-readable OpenAPI 3.1 spec at /openapi.json.
  • Documented v1 versioning/stability policy and data licensing on this page.
  • Added embeddable race widgets at /embed/race/{raceId} — copy-paste iframe snippet documented in the Embeds section below.