Election results API
Version 1. Last changed 2026-09-25.On election night Open America draws its district map and the Senate board from one JSON feed of counts published by official state election offices. That feed is public. This page is its contract, written for a developer who has not seen the site before.
live is true, send Accept-Encoding: gzip and an
If-None-Match header, and expect nothing new between polls: a cache answers anything faster.
Endpoints
GET /elections/results.json
Every race the site holds for one office in one election year, keyed by seat. This is the object the map polls.
| Query parameter | Values | Meaning |
|---|---|---|
year |
2026 (default), 2024 | The general election to read. 2024 is a real replay of that year's certified results, stored by the same pipeline, and stays available for testing a client. Any other value is treated as 2026. Never a 404. |
office |
House (default), Senate, Governor | Case does not matter. Any other value is treated as House. Never a 404. Governor races are stored for the states whose feed carries them; the site has no page for them yet, but the feed serves them. |
The response echoes the year and office it served, so a client can tell when a parameter was ignored.
GET /elections/seat/<ST-NN>.json
One House seat: who holds it today and the record at a glance. ST is the two-letter state code,
NN the two-digit district, 00 for an at-large seat. Case does not matter on the way in.
An unknown state, a district the state does not have, or a key that is not two letters, a dash and two digits is a 404.
This endpoint knows nothing about election results; it is the panel the map opens when a district is clicked.
The results payload
Field by field, with the JSON type. A type followed by "or null" means the value can be JSON null.
Percentages are numbers from 0 to 100, not fractions. Timestamps are ISO 8601 with an offset, in UTC.
Top level
| Field | Type | Meaning |
|---|---|---|
api | integer | The contract version, currently 1. See Versioning. |
year | integer | The election year served. |
office | string | The office served: House, Senate or Governor. |
live | boolean | Votes are being counted and the feed is fresh. Exact rule below. |
stale | boolean | Votes are being counted but the feed is not fresh. Exact rule below. |
counting | boolean | At least one race has votes. |
final | boolean | Every race in the payload reports all of its precincts in. |
as_of | string or null | The newest "last updated" timestamp any state feed gave, across every race in the payload. Null when no race has one. This is the timestamp to quote with a count. |
source | string | "state": official state election offices. Empty when the payload holds no races. |
counts | object | Totals over the races in the payload. Fields below. |
races | object | One entry per race, keyed by seat. Key format and fields below. |
counts
| Field | Type | Meaning |
|---|---|---|
races | integer | How many races are in the payload. |
with_precincts | integer | How many of them come from a state that publishes a precinct count at all. |
reporting | integer | How many races report 100 percent of precincts in. This is the number the site's status line reads out as districts with all precincts in. It counts precincts, not calls. |
not_called | integer | Races without a call. On this feed it always equals races, because no state feed calls a race and this site never will. |
called | object | Party code to number of called races. Always empty on this feed, for the same reason. It is in the shape because the underlying model can carry a call from a source that makes them; no registered source does. |
races[key]
Keys are seats. House: TX-23, two-digit district, 00 for at-large, so Alaska is AK-00.
Senate: the state code alone, GA; a Senate special election is FL-special, its own key, so it can never overwrite a regular race in the same state.
Governor: the state code alone. House special elections are not in the payload: they share a seat key with the general election on the same seat.
| Field | Type | Meaning |
|---|---|---|
leader | string | Party code of the candidate with the most votes so far: D, R, I (independent or unaffiliated), L, G, C (Constitution), W (Working Families). Empty while the race has no votes. A leader is not a winner. |
called | boolean | Always false on this feed. |
winner | string | Party code of a declared winner. Always empty on this feed. |
pct_in | number or null | Percent of precincts reporting, as the state publishes it. Null when the state publishes no precinct count; such a race can never make final true. |
total | integer | Total votes counted in the race so far. When the state gives no total it is the sum of the candidates' votes. |
uncontested | boolean | The feed lists one candidate. Some states leave an unopposed candidate off the ballot entirely; that seat is then absent, not uncontested. |
cands | array | The candidates, most votes first, ties by last name. Fields below. |
races[key].cands[]
| Field | Type | Meaning |
|---|---|---|
n | string | Name, first then last, as the state spells it. |
p | string | Party code, as for leader. Empty for a nonpartisan line. |
v | integer | Votes so far. |
pct | number or null | Share of the race's votes, 0 to 100. Null before any vote is counted. |
inc | boolean | True only when the state's feed flags the candidate as the incumbent. Most feeds carry no such flag, so false means nothing. For who holds the seat today, use the seat endpoint. |
w | boolean | Declared winner. Always false on this feed. |
b | string | The congress.gov bioguide id when a flagged incumbent was matched to exactly one sitting member of that seat, otherwise empty. Never a guess. |
What live, stale, counting and final mean
The four flags are computed on every response from the stored races and the site's own sync log. They are not independent; read them together.
countingis true when at least one race in the payload has votes. Before the first count arrives it is false, even if the states have already published files of zeros.- The feed is fresh when the newest state update (
as_of) is less than 3 hours old and the site's own sync of the state feeds last succeeded less than 15 minutes ago. The sync runs every five minutes, and every minute on election night. liveiscountingand fresh and notfinal.staleiscountingand not fresh and notfinal. The count you hold is real but may be behind;as_ofsays how far.finalis true when every race in the payload has a precinct count and every one of them is at 100 percent. It is never "every race was called". A state that publishes no precinct counts keeps the payload out offinal, which is the honest answer. Oncefinalis true,liveandstaleare both false.
| counting | live | stale | final | What it means |
|---|---|---|---|---|
| false | false | false | false | No votes yet. Before election night this is the only state you will see, with races empty. |
| true | true | false | false | Counting, and the numbers are current. Poll every 60 seconds. |
| true | false | true | false | Counting, but the feed or the sync has fallen behind. Show as_of next to any number. |
| true | false | false | true | Every race in the payload has all precincts in. The canvass can still change a count; final is about precincts, not certification. |
The seat payload
| Field | Type | Meaning |
|---|---|---|
key | string | The seat key, upper case, as in races. |
state | string | Two-letter state code. |
cd | string | Two-digit district, 00 for at-large. |
title | string | The seat the way a person says it: "Texas 23rd district", "Alaska at-large". |
status | string | held or vacant. |
member | string | The sitting member's name. Empty when vacant. |
bioguide | string | The member's congress.gov bioguide id. Empty when vacant. |
party | string | D, R or I for anything else. Empty when vacant. |
on_ballot | boolean | Whether the seat is on the 2026 ballot. Every House seat is. |
img | string | Path to the member's portrait on this site, or empty. |
detail_url | string | Held seats only. Path to the member's page on this site. |
stats | object | Held seats only. unity (party unity, percent), missed (missed votes, percent) and sponsored (bills sponsored, count), each a number or null when not yet computed. Defined on the methodology page. |
reason | string | Vacant seats only. Why, as the House Clerk lists it: "Resigned", "Died". |
Caching and how often to poll
/elections/results.jsonis served withCache-Control: public, max-age=30, s-maxage=60: a browser may reuse it for 30 seconds, the edge cache for 60. The site sits behind a shared edge cache that honours those headers, so a second request inside the window is answered by the cache with the same bytes./elections/seat/<ST-NN>.jsonis served withCache-Control: public, max-age=900, s-maxage=3600. It changes when a member is sworn in or a seat falls vacant, not on election night.- Poll every 60 seconds while
liveis true. That is the map's own schedule and it matches the edge TTL; polling faster only re-reads the cache. Whenliveis false the map backs off to 15 minutes while races exist and 10 minutes before any do, and doubles its wait on each failed request up to 10 minutes. A client that copies that schedule is a good citizen. - Every response carries an
ETag. Send it back asIf-None-Matchand an unchanged payload is a 304 with no body. Between syncs nothing changes, so most polls on election night should be 304s. - Send
Accept-Encoding: gzip. Measured on 2026-09-25 against the live site, the 2024 House payload (198 races) was 71 KB as JSON and 13 KB on the wire with gzip, which is what the edge returns for a client that offersbr, gzip. The 2026 payload will cover more seats and be larger in proportion. - There is no rate limit on these endpoints and no key. Please identify your client with a descriptive
User-Agentand back off on a 429 or a 5xx, as the acceptable use policy asks. - The endpoints do not send a CORS header today, so a page on another site cannot fetch them directly from the browser. Fetch from your server, or proxy. Cross-origin access is planned.
Coverage
A state is in the feed when its election office publishes counts in a machine-readable form this site has an adapter for. The list below is generated from the same registry the sync runs, so it is the list the map and the Senate board use. Today that is 22 states:
- Arkansas (
AR), election office - Arizona (
AZ), election office - Colorado (
CO), election office - Connecticut (
CT), election office - Florida (
FL), election office - Georgia (
GA), election office - Iowa (
IA), election office - Idaho (
ID), election office - Indiana (
IN), election office - Louisiana (
LA), election office - Michigan (
MI), election office - Minnesota (
MN), election office - North Carolina (
NC), election office - Pennsylvania (
PA), election office - Rhode Island (
RI), election office - South Carolina (
SC), election office - Texas (
TX), election office - Utah (
UT), election office - Virginia (
VA), election office - Vermont (
VT), election office - Washington (
WA), election office - West Virginia (
WV), election office
A seat in any other state does not appear in races at all. Do not read its absence as "no results yet"; the site shows those seats as "no live feed from this state" and, on the Senate board, links to the state's election office instead.
Coverage within a state follows the feed: a state that publishes no precinct counts gives pct_in null on every race, and a state that leaves an unopposed candidate off its file gives no race for that seat.
What this feed is not
- It does not call races.
called,winnerandware always false or empty. A leader with 100 percent of precincts in is still a leader, until the state certifies. - It does not project, estimate, model or adjust. There is no expected vote, no needle, no "likely" anything.
- It does not list candidates before results exist. A race appears when a state feed publishes it and the site has stored it, which on election night starts after the first polls close.
- It is not a national feed. It covers the 22 states above.
- It is not a vendor product. The site declined a commercial results API and reads the states directly; there is no contract, no key and no bill for it.
Source and attribution
The counts belong to the states that published them. When you show a number from this feed, say where it came from:
"official state election offices via Open America (openamerica.io)", with a link back where the format allows, and the as_of timestamp next to it, because a count changes minute to minute on the night.
Use is free, paid or unpaid, for journalism, research, classrooms and civic projects, on the same terms as everything else on the
methodology page. Repackaging the live feed as your own results service is the one thing to ask about first:
[email protected].
Versioning
The api field is the version of this contract. Adding a field keeps the number; a field that is renamed, removed or changes type bumps it,
and this page is updated in the same change. The site's own tests snapshot the shape described here, so the payload cannot drift from the page silently.
A client should ignore fields it does not know and should check api before trusting a field it does.
Example
The rehearsal year, which is safe to hit at any time:
curl --compressed -H 'User-Agent: my-dashboard/1.0 ([email protected])' \
'https://openamerica.io/elections/results.json?year=2024&office=House'
Trimmed to one race:
{
"api": 1,
"year": 2024,
"office": "House",
"live": false,
"stale": true,
"counting": true,
"final": false,
"as_of": "2026-09-23T17:47:54+00:00",
"source": "state",
"counts": {"called": {}, "races": 198, "not_called": 198, "reporting": 169, "with_precincts": 171},
"races": {
"AZ-01": {
"leader": "R",
"called": false,
"winner": "",
"pct_in": 100.0,
"total": 434504,
"uncontested": false,
"cands": [
{"n": "David Schweikert", "p": "R", "v": 225538, "pct": 51.91, "inc": false, "w": false, "b": ""},
{"n": "Amish Shah", "p": "D", "v": 208966, "pct": 48.09, "inc": false, "w": false, "b": ""}
]
}
}
}
The counts were captured from the live site on 2026-09-25. stale is true because the rehearsal was stored months after the election, so nothing about it is fresh; that is the flag doing its job.
final is false although this race is at 100 percent: 27 of the 198 races come from states that publish no precinct count, and 2 more had not reached 100 when the archive was read. counts.reporting tells you how many have.