Election Atlas serves one canonical election model for discovery, filtering, and detail pages, with live extensions for race results and geography when detailed reporting is available.
The important split is simple: use the canonical routes for stable records, and call the live results or map routes only when you want current reporting.
Every public read route is also available under /api/v1 for consumers that want an explicit versioned base path.
Examples below reflect the current API shape as served by the running app, trimmed to the fields most useful to consumers of Election Atlas.
The public read routes are open by default and are safe to call from other applications. They return CORS headers and support OPTIONS preflight requests.
The write route POST /api/admin/import is not part of the public integration surface. It is environment-gated in production and should be treated as an internal admin endpoint.
Imports also require a reachable write database. If the app is running in read-only snapshot mode, the route returns a structured error instead of crashing.
Point your app at the deployed Election Atlas base URL and call the read routes directly. The same JSON surface works for server-rendered apps, background jobs, and browser clients.
Use list routes to discover atlas ids first, then request /[id], /[id]/results, or /[id]/map when you need a fuller surface.
const baseUrl = "https://your-domain.com/api"; const response = await fetch( `${baseUrl}/elections?year=2020&date=2020-11-03&type=presidential`); if (!response.ok) { throw new Error("Election Atlas request failed");} const payload = await response.json();console.log(payload.data);fetch("https://your-domain.com/api/health") .then((response) => response.json()) .then((payload) => { console.log(payload.ok); console.log(payload.snapshot); });Public read routes return Access-Control-Allow-Origin, X-Request-Id, and X-Election-Atlas-Version headers.
Canonical list and detail routes use a longer shared-cache window, while live results and map routes use shorter cache windows so reporting updates can move through faster.
GET /api/elections supports limit and offset. The response includes a meta.pagination block so consumers can page deterministically.
Use GET /api/elections to query the canonical ledger. This is the same read surface used by the homepage, calendar, and race directory.
Hold onto the returned atlas ids, then use them with the detail, results, or map endpoints.
fetch("/api/elections?year=2020&date=2020-11-03&type=presidential") .then((response) => response.json()) .then((payload) => { console.log(payload.data); console.log(payload.meta); });{ "data": [ { "id": "2020-11-03-29107", "slug": "2020-11-03-29107", "title": "Arizona US President", "date": "2020-11-03", "endDate": null, "timezone": null, "countryCode": "US", "countryName": "United States", "region": "AZ", "locality": null, "jurisdictionName": "AZ", "electionType": "presidential", "status": "unofficial_results", "description": "Arizona US President in AZ. Current reporting shows 100% reporting, 3 listed candidates.", "lastSyncedAt": "2026-03-09T01:49:56.880Z", "liveMapAvailable": true } ], "meta": { "total": 5, "filters": { "year": "2020", "date": "2020-11-03", "type": "presidential" }, "countsByStatus": { "unofficial_results": 5 }, "countsByType": { "presidential": 5 }, "pagination": { "limit": 50, "offset": 0, "returned": 5 } }}Return a lightweight health payload for the public API surface.
Useful for uptime checks, deployment verification, and snapshot visibility.
List canonical Election Atlas records from the normalized read model.
Return elections happening on the server's current local date.
Return upcoming normalized records from today onward.
Fetch one canonical election record by Election Atlas id.
Atlas ids are stable record identifiers and currently mirror the canonical slug.
fetch("/api/elections/2020-11-03-29107") .then((response) => response.json()) .then((payload) => { console.log(payload.title); console.log(payload.jurisdiction); });{ "id": "2020-11-03-29107", "slug": "2020-11-03-29107", "title": "Arizona US President", "date": "2020-11-03", "endDate": null, "timezone": null, "countryCode": "US", "countryName": "United States", "region": "AZ", "locality": null, "jurisdictionName": "AZ", "electionType": "presidential", "status": "unofficial_results", "description": "Arizona US President in AZ. Current reporting shows 100% reporting, 3 listed candidates.", "lastSyncedAt": "2026-03-09T23:35:52.645Z", "liveMapAvailable": true, "createdAt": "2026-03-09T23:35:52.645Z", "updatedAt": "2026-03-09T23:35:52.645Z", "jurisdiction": { "id": "jurisdiction:us-az-az", "slug": "us-az-az", "displayPath": "AZ, AZ, United States" }}The live routes sit beside the canonical model. They are the reporting edge of Election Atlas, not the stable core record, and they can return 404 when live reporting or a renderable map is unavailable.
fetch("/api/elections/2020-11-03-29107/results") .then((response) => response.json()) .then((payload) => { console.log(payload.percentReporting); console.log(payload.candidates); console.log(payload.regions); });{ "mapAvailable": true, "mapName": "Arizona", "lastUpdated": "2026-03-08T21:54:00.000Z", "percentReporting": 99.3, "candidates": [ { "name": "Joe Biden", "party": "Democratic", "votes": 1672143, "percent": 49.36, "winner": true, "color": "#4874a3" }, { "name": "Donald J. Trump", "party": "Republican", "votes": 1661686, "percent": 49.06, "winner": false, "color": "#c6606b" } ], "regions": [ { "key": "maricopa", "name": "Maricopa", "type": "county", "fill": "#4D91FF", "percentReporting": 100, "totalVotes": 2060523, "candidates": [] } ]}fetch("/api/elections/2020-11-03-29107/map") .then((response) => response.json()) .then((payload) => { console.log(payload.topCandidates); console.log(payload.mapSvg); });{ "mapSvg": "<svg ... />", "topCandidates": [ { "name": "Joe Biden", "party": "Democratic", "votes": 1672143, "percent": 49.36, "winner": true, "color": "#4874a3" }, { "name": "Donald J. Trump", "party": "Republican", "votes": 1661686, "percent": 49.06, "winner": false, "color": "#c6606b" } ]}Fetch live results for one race when reporting exists.
Returns 404 when the selected race does not expose live reporting.
Fetch a compact rendered map preview plus the top two candidates.
Returns 404 when the selected race does not expose a renderable map.
Every imported record is normalized into the Election Atlas schema before it reaches the main read routes. The public surface stays narrow on purpose so filters, ids, and route contracts remain stable over time.
The canonical read model is imported through adapters, normalized once, and then served through a snapshot-backed layer for local stability.
Read requests can trigger background snapshot refresh when the current snapshot is stale. Imports also refresh the served snapshot after ingest, so the directory and the API stay aligned.
Live results and map routes fetch reporting detail on demand. They stay separate because reporting can change faster than the normalized record itself.
The import body must use a real adapter key accepted by the route.
fetch("/api/admin/import", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source: "local-static" })});Trigger an adapter import run and return the import summary.
Internal admin route. Environment-gated in production and requires a reachable write database connection.