API reference
Elections, contests, endorsers, endorsements and officeholders across U.S. ballots.
The Endorsement Stream endpoints (GET /v1/stream for Server-Sent Events, GET /v1/events for polling, and /v1/subscriptions for saved filters) are in the Stream API reference, with the Endorsement Stream docs.
- Base URL
https://api.ballotdata.ai- Authentication
Authorization: Bearer ebk_…on every request (orX-API-Key: ebk_…). Keys come from the console; keep them on your servers.- Client libraries
npm install ballotdataorpip install ballotdata. The JavaScript and Python samples use them; see client libraries.- Format
- JSON over HTTPS. Dates are
YYYY-MM-DD; times are ISO 8601 in UTC. - OpenAPI
- openapi.json (OpenAPI 3.1.0, version 1.5.0): import it into Postman, Insomnia or a client generator.
Paging
Lists return { "data": [...], "next_cursor": "…" }. Pass cursor=<next_cursor> for the next page; null means you have them all. limit sets the page size.
Filters
List parameters take comma-separated values, and any of them matches: states=CA,NV. Different parameters combine with AND.
Errors and limits
Errors carry a code you can branch on, a message, and a request_id to quote when you write to us. Every response says where you stand on your plan’s limits in RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (and X-Daily-Limit / X-Daily-Remaining). Over a limit you get 429 with Retry-After. See keys, limits and errors.
Example responses are illustrative: the shapes are exact, the people and picks are made up.
curl "https://api.ballotdata.ai/v1/endorsements?states=CA&categories=labor" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl "https://api.ballotdata.ai/v1/endorsements?elections=ca-2026-11-03-general&cursor=aTo5MDIxMQ" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"error": {
"code": "rate_limit_exceeded",
"message": "Your plan allows 600 requests a minute.",
"request_id": "8c1f2a4e-6b7d-4c3e-9f10-2a3b4c5d6e7f"
}
}Elections
States, elections and coverage: what there is to ask about.
List states
GET /v1/states
Every state, D.C. and territory, with its USPS and FIPS codes. Use the codes in states= filters.
Returns
200 Every state.
dataarray of StateEvery item.
3 fields
codestringUSPS state code.
fipsstringTwo-digit FIPS code.
namestringFull name.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/states" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"code": "AK",
"fips": "02",
"name": "Alaska"
},
{
"code": "AL",
"fips": "01",
"name": "Alabama"
},
{
"code": "AR",
"fips": "05",
"name": "Arkansas"
}
]
}List elections
GET /v1/elections
Elections, with their voting rules in details (all-mail, ranked choice, fusion, runoff dates) and how many contests, endorsements and results each has. Past elections kept for their results (the November 5, 2024 general in every state) are listed too, with details.historical; upcoming=true leaves them out. Use an election’s slug to ask for its contests or endorsements.
Query parameters
statescomma-separated stringsUSPS codes.
ExampleCAtypescomma-separated stringsKinds of election (general, primary, special, runoff and so on).
One ofgeneral,primary,special,special_primary,runoff,consolidated.Examplegeneralupcomingbooleantrue for only elections from yesterday on; false (the default) for every election.
Defaultfalse.Exampletruefromstring (date)Elections on or after this date.
Example2026-11-01tostring (date)Elections on or before this date.
Example2026-12-31langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–500.
Default100.1 to 500.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of elections, by id.
dataarray of ElectionSummaryThis page.
13 fields
idintegerElection id.
slugstringElection slug, used in elections= filters: <state>-<date>-<type>.
namestringElection name.
short_namestring or nullShorter name.
datestring (date)Election day.
statestring or nullState; null for a multi-state election.
typestringKind of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.primary_systemstring or nullHow the state runs primaries.
One ofnone,closed,semi_closed,open,top_two,nonpartisan.info_urlstring (uri) or nullOfficial election page.
detailsobjectVoting rules for the election. Only keys that apply are present.
12 fields
all_mailbooleanEvery voter is mailed a ballot.
ranked_choicebooleanSome contests in this election use ranked choice voting.
fusion_votingbooleanCandidates can appear on more than one party’s line (New York, Connecticut).
partisan_primarybooleanA party primary.
primarystringWhich primary, where a state holds several.
consolidated_withstringElections held on the same ballot.
runoff_datestring (date)Date of a runoff, if one is needed.
runoff_election_datestring (date)Date of the runoff election.
top_two_primary_datestring (date)Date of the top-two primary that chose these candidates.
louisiana_open_primarybooleanLouisiana’s open (jungle) primary.
notestringAnything else voters should know.
historicalbooleanA past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.
contest_countintegerContests on the ballot.
endorsement_countintegerEndorsements in those contests.
result_countintegerContests on the ballot with a result.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/elections?states=CA&upcoming=true" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/elections" \
--data-urlencode "states=CA" \
--data-urlencode "types=general" \
--data-urlencode "upcoming=true" \
--data-urlencode "from=2026-11-01" \
--data-urlencode "to=2026-12-31" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
},
"contest_count": 1184,
"endorsement_count": 9632,
"result_count": 1012
},
{
"id": 812,
"slug": "ca-2024-11-05-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2024-11-05",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true,
"historical": true
},
"contest_count": 1630,
"endorsement_count": 0,
"result_count": 1630
}
],
"next_cursor": "aToz"
}Coverage by state
GET /v1/coverage
What the API has for each state: elections, contests, measures, endorsers and endorsements, and when they last changed. Handy for deciding what to sync.
Query parameters
upcomingbooleantrue (the default) counts only upcoming elections; false counts every election.
Defaulttrue.Examplefalse
Returns
200 One row per state.
dataarray of CoverageEvery item.
11 fields
statestringUSPS state code.
namestringState name.
election_countintegerElections.
contest_countintegerContests.
measure_countintegerOf those, ballot measures.
endorser_countintegerEndorsers with endorsements here.
endorsement_countintegerEndorsements.
endorsed_contest_countintegerContests with at least one endorsement.
first_electionstring (date) or nullEarliest election.
last_electionstring (date) or nullLatest election.
updated_atstring (date-time) or nullWhen a contest or endorsement last changed.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/coverage" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/coverage" \
--data-urlencode "upcoming=false" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"state": "CA",
"name": "California",
"election_count": 1,
"contest_count": 1184,
"measure_count": 402,
"endorser_count": 611,
"endorsement_count": 9632,
"endorsed_contest_count": 730,
"first_election": "2026-11-03",
"last_election": "2026-11-03",
"updated_at": "2026-10-21T14:02:11.408Z"
}
]
}Download a whole election
GET /v1/elections/{slug}/export
Everything you can read in one election in one response: the election, and each contest with its choices, its result once a source has one, and every endorser’s position, in ballot order. Past elections (details.historical, such as the November 5, 2024 general) export the same way. format=csv gives one row per endorsement instead, each repeating its election, contest, district, choice, the choice’s result and the endorser (contests no one endorsed in have no rows).
Path parameters
slugstringrequiredElection slug (from GET /v1/elections).
Exampleca-2026-11-03-general
Query parameters
formatstringjson (the default) or csv.
One ofjson,csv.Defaultjson.Examplecsvlangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The election. With format=csv, a CSV file (text/csv) with the columns election_slug, election_date, contest_id, contest_title, contest_kind, vote_method, votes_allowed, ranks_allowed, measure_code, district_id, district_name, district_level, district_ocd_id, state, choice_id, choice_label, measure_answer, candidate_id, candidate_name, party, incumbent, withdrawn, endorsement_id, endorser_id, endorser_slug, endorser_name, endorser_category, endorser_state, rank, statement, source_url, published_on, added_by, choice_votes, choice_vote_pct, choice_winner, result_status, result_source_kind, result_total_votes, result_source_url.
dataElectionExport2 fields
electionElection10 fields
idintegerElection id.
slugstringElection slug, used in elections= filters: <state>-<date>-<type>.
namestringElection name.
short_namestring or nullShorter name.
datestring (date)Election day.
statestring or nullState; null for a multi-state election.
typestringKind of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.primary_systemstring or nullHow the state runs primaries.
One ofnone,closed,semi_closed,open,top_two,nonpartisan.info_urlstring (uri) or nullOfficial election page.
detailsobjectVoting rules for the election. Only keys that apply are present.
12 fields
all_mailbooleanEvery voter is mailed a ballot.
ranked_choicebooleanSome contests in this election use ranked choice voting.
fusion_votingbooleanCandidates can appear on more than one party’s line (New York, Connecticut).
partisan_primarybooleanA party primary.
primarystringWhich primary, where a state holds several.
consolidated_withstringElections held on the same ballot.
runoff_datestring (date)Date of a runoff, if one is needed.
runoff_election_datestring (date)Date of the runoff election.
top_two_primary_datestring (date)Date of the top-two primary that chose these candidates.
louisiana_open_primarybooleanLouisiana’s open (jungle) primary.
notestringAnything else voters should know.
historicalbooleanA past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.
contestsarray of objectEvery contest you can read in the election, in ballot order: races from federal down, then measures.
20 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
short_titlestring or nullShorter title.
kindstringcandidate (an office), measure (a proposition) or retention (keep a judge?).
One ofcandidate,measure,retention.vote_methodstringHow votes are cast: plurality (vote for one), multi_seat (vote for up to votes_allowed), ranked_choice (rank up to ranks_allowed), approval, or yes_no.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullHow many choices a voter may mark (vote for N).
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
measure_codestring or nullThe measure’s letter or number: “K”, “Prop 1”.
descriptionstring or nullA short description or question. For a measure with ballot_text, often a summary.
ballot_textstring or nullA measure’s wording exactly as printed on the ballot; null where we don’t have it yet.
full_text_urlstring (uri) or nullThe official full text of a measure.
unopposedbooleanA candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.
ballot_orderinteger or nullPosition on the printed ballot.
partyParty or nullFor a party primary, the party.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
detailsobjectFacts about the contest. Only keys that apply are present.
18 fields
uncontestedbooleanOnly one candidate is running.
nonpartisanbooleanCandidates appear without a party.
measure_typestringbond, charter_amendment, initiative, referendum, tax, …
vote_requiredstringShare of the vote needed to pass, e.g. "2/3" or "55%".
majority_requiredbooleanA candidate needs a majority to win outright (else a runoff).
amountnumberDollar amount of a bond or tax measure.
termstringLength of the term, e.g. "4 years".
short_termbooleanThe seat is for the remainder of an unexpired term.
justicestringThe judge or justice a retention question is about.
runoff_datestring (date)Date of a runoff, if one is needed.
stagestringStage of a multi-round contest.
elected_at_largebooleanElected by the whole jurisdiction, not by district.
none_of_thesebooleanThe ballot offers “None of these candidates” (Nevada).
primary_nominatingbooleanThe primary nominates a party’s candidate.
winnersintegerNumber of winners, where it differs from votes_allowed.
ref_typestringKind of referendum.
party_lines_unverifiedbooleanThe candidates are known, but their party lines haven’t been checked against a printed ballot yet.
question_wordingstringnotice where description summarizes an official notice (Ohio) instead of quoting the ballot.
source_urlstring (uri) or nullThe official source the contest was loaded from.
districtDistrictThe district a contest is voted in.
6 fields
idintegerDistrict id. Matches an officeholder’s district_id.
ocd_idstring or nullOpen Civic Data division id, used in districts= filters.
namestringDistrict name.
levelstringLevel of government.
One offederal,state,county,city,school,special.typestringKind of district: statewide, congressional, state_upper, state_lower, county, council, school, …
statestring or nullUSPS state code.
resultContestResult or nullThe election result, once a source has one; null before then.
17 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest: the printed choices plus write-ins. null on a party-reported result.
precincts_reportinginteger or nullPrecincts counted so far, where the source says.
precincts_totalinteger or nullPrecincts in the contest, where the source says.
sourceobjectThe source of the result.
4 fields
namestringWho published the result.
urlstring (uri)Where it was published: the contest’s own results page where the source has one. Every result has one.
contest_namestring or nullThe contest’s name in the source, which may differ from the printed title.
updated_atstring (date-time) or nullWhen the source last updated it, if it says.
official_urlstring (uri) or nullFor a compiled result, the state’s own certified results, where we have them.
more_sourcesarray of objectFurther announcements of the same result (party-reported results that more than one party published).
2 fields
namestring or nullWho published it.
urlstring (uri)Where.
write_in_votesinteger or nullVotes for write-in candidates, where the source counts them.
blank_votesinteger or nullBlank votes, where the source counts them.
over_votesinteger or nullOvervotes, where the source counts them.
under_votesinteger or nullUndervotes, where the source counts them.
other_choicesarray of OtherResultLineLines the source reports that aren’t on the printed ballot, such as named write-ins.
3 fields
labelstring or nullAs the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.
votesinteger or nullVotes for it.
write_inbooleanA write-in candidate.
rcv_roundsarray of object or nullRanked choice: the rounds of the official tabulation, as the source reports them; null for other contests or where we don’t have them.
notesstring or nullAnything else about the result, such as how a ranked-choice winner was decided.
fetched_atstring (date-time)When we last read it from the source.
choicesarray of ChoiceEvery choice, in ballot order.
12 fields
idintegerChoice id: the choice_id endorsements refer to.
labelstringAs printed: the candidate’s ballot name, or Yes / No.
measure_answerstring or nullyes or no for a measure or retention question; null for a candidate.
One ofyes,no.optionstring or nullA special line: none_of_these (Nevada).
One ofnone_of_these.ballot_designationstring or nullOccupation line printed under the name, e.g. “Transit Planner”.
withdrawnbooleanThe candidate withdrew but is still printed on the ballot.
incumbentboolean or nullThe candidate holds this seat now: true or false where we know, null where we don’t yet (and for a measure answer).
vote_meaningstring or nullFor a measure answer: what a Yes (or No) vote means, as the election office words it.
candidateCandidate or nullThe candidate; null for a measure answer.
4 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
partyParty or nullThe party (or party preference) printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs (fusion lines, top-two preferences).
resultChoiceResult or nullThis choice’s votes, once the contest has a result; null before then.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
endorsementsarray of PositionEvery endorser’s position, by endorser name.
2 fields
endorserEndorserRefWho made an endorsement.
5 fields
idintegerEndorser id.
slugstringEndorser slug, used in endorsers= filters.
namestringEndorser name.
categoryCategoryThe kind of endorser.
statestring or nullHome state; null for national endorsers.
picksarray of PickOne pick for vote-for-one, up to N for vote-for-N, the ranking for ranked choice, Yes or No for a measure.
9 fields
endorsement_idintegerThe endorsement’s id (GET /v1/endorsements/{id}).
choice_idintegerThe Choice endorsed.
labelstringThat choice’s label.
measure_answerstring or nullyes or no for a measure.
One ofyes,no.rankinteger or nullRanked choice: 1 for first choice, 2 for second, …; null otherwise.
statementstring or nullWhat the endorser said about it.
source_urlstring (uri) or nullWhere the endorsement was published.
published_onstring (date) or nullWhen it was published.
added_bystringHow the endorsement got here: automatic (added by our automatic watch of the endorser’s own page), staff (entered by hand by BallotData staff) or data_load (loaded in a researched data pass).
One ofautomatic,staff,data_load.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/elections/ca-2026-11-03-general/export?format=csv" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/elections/ca-2026-11-03-general/export" \
--data-urlencode "format=csv" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
}
},
"contests": [
{
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"short_title": "Supervisor, D8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10,
"measure_code": null,
"description": null,
"ballot_text": null,
"full_text_url": null,
"unopposed": false,
"ballot_order": 41,
"party": null,
"details": {
"nonpartisan": true
},
"source_url": "https://www.sf.gov/departments/department-elections",
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"type": "council",
"state": "CA"
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"precincts_reporting": 41,
"precincts_total": 41,
"source": {
"name": "San Francisco Department of Elections",
"url": "https://www.sf.gov/departments/department-elections/results",
"contest_name": "Member, Board of Supervisors, District 8",
"updated_at": "2026-11-12T17:00:00-08:00"
},
"official_url": null,
"more_sources": [],
"write_in_votes": 212,
"blank_votes": null,
"over_votes": null,
"under_votes": null,
"other_choices": [
{
"label": "Write-in",
"votes": 212,
"write_in": true
}
],
"rcv_rounds": [
{
"round": 1,
"tallies": {
"Jane Doe": 24133,
"Sam Rivera": 21907,
"Lee Park": 6166
},
"eliminated": ["Lee Park"]
},
{
"round": 2,
"tallies": {
"Jane Doe": 27310,
"Sam Rivera": 23998
}
}
],
"notes": "Ranked choice: Jane Doe leads after round 2.",
"fetched_at": "2026-11-13T06:30:12Z"
},
"choices": [
{
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"incumbent": true,
"vote_meaning": null,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
},
{
"id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"option": null,
"ballot_designation": "Small Business Owner",
"withdrawn": false,
"incumbent": false,
"vote_meaning": null,
"candidate": {
"id": 8812,
"full_name": "Sam Rivera",
"website_url": null,
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 21907,
"vote_pct": 41.793,
"winner": null
}
}
],
"endorsements": [
{
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"picks": [
{
"endorsement_id": 90211,
"choice_id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"added_by": "data_load"
},
{
"endorsement_id": 90212,
"choice_id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"added_by": "data_load"
}
]
},
{
"endorser": {
"id": 88,
"slug": "sf-chronicle",
"name": "San Francisco Chronicle",
"category": {
"slug": "editorial",
"name": "Voter guides & editorial boards"
},
"state": "CA"
},
"picks": [
{
"endorsement_id": 90377,
"choice_id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"rank": null,
"statement": "Rivera has the clearer plan for the district’s corridors.",
"source_url": "https://www.sfchronicle.com/opinion/endorsements",
"published_on": "2026-10-02",
"added_by": "automatic"
}
]
}
]
}
]
}
}Contests
The races on a ballot and their choices: candidates, or Yes/No for a measure. Contests carry their voting rules: vote for one, vote for N (votes_allowed), ranked choice (ranks_allowed), or yes/no, and their election results once a source has them: official counts from election offices, certified returns compiled by researchers for past elections, or a party’s announcement of the winner, always with the source’s URL.
List contests
GET /v1/contests
The races on the ballot, each with its district, its election, its voting rules, every choice in ballot order and, once a source has one, its result (votes for each choice and the winners). Filter by election, state, level, kind, district or result. To see who endorsed whom, use GET /v1/contests/{id} or /v1/endorsements.
Query parameters
electionscomma-separated stringsElection slugs (GET /v1/elections).
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAlevelscomma-separated stringsLevels of the contest’s district, from federal down to school and special districts.
One offederal,state,county,city,school,special.Examplecounty,citycontest_kindscomma-separated stringsContest kinds: candidate (an office), measure, or retention (whether to keep a judge).
One ofcandidate,measure,retention.Examplemeasuredistrictscomma-separated stringsOCD division ids of the contests’ districts.
Exampleocd-division/country:us/state:ca/county:san_franciscoupcomingbooleantrue for only elections from yesterday on; false (the default) for every election.
Defaultfalse.Exampletruehas_resultbooleantrue for only contests with a result; false for only contests without one yet. Leave it out for both.
Exampletrueresult_statusescomma-separated stringsOnly contests whose result has one of these statuses: unofficial (counts still changing) or certified.
One ofunofficial,certified.Examplecertifiedlangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–200.
Default50.1 to 200.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of contests, by id.
dataarray of ContestThis page.
20 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
short_titlestring or nullShorter title.
kindstringcandidate (an office), measure (a proposition) or retention (keep a judge?).
One ofcandidate,measure,retention.vote_methodstringHow votes are cast: plurality (vote for one), multi_seat (vote for up to votes_allowed), ranked_choice (rank up to ranks_allowed), approval, or yes_no.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullHow many choices a voter may mark (vote for N).
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
measure_codestring or nullThe measure’s letter or number: “K”, “Prop 1”.
descriptionstring or nullA short description or question. For a measure with ballot_text, often a summary.
ballot_textstring or nullA measure’s wording exactly as printed on the ballot; null where we don’t have it yet.
full_text_urlstring (uri) or nullThe official full text of a measure.
unopposedbooleanA candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.
ballot_orderinteger or nullPosition on the printed ballot.
partyParty or nullFor a party primary, the party.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
detailsobjectFacts about the contest. Only keys that apply are present.
18 fields
uncontestedbooleanOnly one candidate is running.
nonpartisanbooleanCandidates appear without a party.
measure_typestringbond, charter_amendment, initiative, referendum, tax, …
vote_requiredstringShare of the vote needed to pass, e.g. "2/3" or "55%".
majority_requiredbooleanA candidate needs a majority to win outright (else a runoff).
amountnumberDollar amount of a bond or tax measure.
termstringLength of the term, e.g. "4 years".
short_termbooleanThe seat is for the remainder of an unexpired term.
justicestringThe judge or justice a retention question is about.
runoff_datestring (date)Date of a runoff, if one is needed.
stagestringStage of a multi-round contest.
elected_at_largebooleanElected by the whole jurisdiction, not by district.
none_of_thesebooleanThe ballot offers “None of these candidates” (Nevada).
primary_nominatingbooleanThe primary nominates a party’s candidate.
winnersintegerNumber of winners, where it differs from votes_allowed.
ref_typestringKind of referendum.
party_lines_unverifiedbooleanThe candidates are known, but their party lines haven’t been checked against a printed ballot yet.
question_wordingstringnotice where description summarizes an official notice (Ohio) instead of quoting the ballot.
source_urlstring (uri) or nullThe official source the contest was loaded from.
districtDistrictThe district a contest is voted in.
6 fields
idintegerDistrict id. Matches an officeholder’s district_id.
ocd_idstring or nullOpen Civic Data division id, used in districts= filters.
namestringDistrict name.
levelstringLevel of government.
One offederal,state,county,city,school,special.typestringKind of district: statewide, congressional, state_upper, state_lower, county, council, school, …
statestring or nullUSPS state code.
resultContestResult or nullThe election result, once a source has one; null before then.
17 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest: the printed choices plus write-ins. null on a party-reported result.
precincts_reportinginteger or nullPrecincts counted so far, where the source says.
precincts_totalinteger or nullPrecincts in the contest, where the source says.
sourceobjectThe source of the result.
4 fields
namestringWho published the result.
urlstring (uri)Where it was published: the contest’s own results page where the source has one. Every result has one.
contest_namestring or nullThe contest’s name in the source, which may differ from the printed title.
updated_atstring (date-time) or nullWhen the source last updated it, if it says.
official_urlstring (uri) or nullFor a compiled result, the state’s own certified results, where we have them.
more_sourcesarray of objectFurther announcements of the same result (party-reported results that more than one party published).
2 fields
namestring or nullWho published it.
urlstring (uri)Where.
write_in_votesinteger or nullVotes for write-in candidates, where the source counts them.
blank_votesinteger or nullBlank votes, where the source counts them.
over_votesinteger or nullOvervotes, where the source counts them.
under_votesinteger or nullUndervotes, where the source counts them.
other_choicesarray of OtherResultLineLines the source reports that aren’t on the printed ballot, such as named write-ins.
3 fields
labelstring or nullAs the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.
votesinteger or nullVotes for it.
write_inbooleanA write-in candidate.
rcv_roundsarray of object or nullRanked choice: the rounds of the official tabulation, as the source reports them; null for other contests or where we don’t have them.
notesstring or nullAnything else about the result, such as how a ranked-choice winner was decided.
fetched_atstring (date-time)When we last read it from the source.
electionElection10 fields
idintegerElection id.
slugstringElection slug, used in elections= filters: <state>-<date>-<type>.
namestringElection name.
short_namestring or nullShorter name.
datestring (date)Election day.
statestring or nullState; null for a multi-state election.
typestringKind of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.primary_systemstring or nullHow the state runs primaries.
One ofnone,closed,semi_closed,open,top_two,nonpartisan.info_urlstring (uri) or nullOfficial election page.
detailsobjectVoting rules for the election. Only keys that apply are present.
12 fields
all_mailbooleanEvery voter is mailed a ballot.
ranked_choicebooleanSome contests in this election use ranked choice voting.
fusion_votingbooleanCandidates can appear on more than one party’s line (New York, Connecticut).
partisan_primarybooleanA party primary.
primarystringWhich primary, where a state holds several.
consolidated_withstringElections held on the same ballot.
runoff_datestring (date)Date of a runoff, if one is needed.
runoff_election_datestring (date)Date of the runoff election.
top_two_primary_datestring (date)Date of the top-two primary that chose these candidates.
louisiana_open_primarybooleanLouisiana’s open (jungle) primary.
notestringAnything else voters should know.
historicalbooleanA past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.
choicesarray of ChoiceEvery choice, in ballot order.
12 fields
idintegerChoice id: the choice_id endorsements refer to.
labelstringAs printed: the candidate’s ballot name, or Yes / No.
measure_answerstring or nullyes or no for a measure or retention question; null for a candidate.
One ofyes,no.optionstring or nullA special line: none_of_these (Nevada).
One ofnone_of_these.ballot_designationstring or nullOccupation line printed under the name, e.g. “Transit Planner”.
withdrawnbooleanThe candidate withdrew but is still printed on the ballot.
incumbentboolean or nullThe candidate holds this seat now: true or false where we know, null where we don’t yet (and for a measure answer).
vote_meaningstring or nullFor a measure answer: what a Yes (or No) vote means, as the election office words it.
candidateCandidate or nullThe candidate; null for a measure answer.
4 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
partyParty or nullThe party (or party preference) printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs (fusion lines, top-two preferences).
resultChoiceResult or nullThis choice’s votes, once the contest has a result; null before then.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/contests?elections=ca-2026-11-03-general&levels=county&limit=20" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/contests" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "states=CA" \
--data-urlencode "levels=county,city" \
--data-urlencode "contest_kinds=measure" \
--data-urlencode "districts=ocd-division/country:us/state:ca/county:san_francisco" \
--data-urlencode "upcoming=true" \
--data-urlencode "has_result=true" \
--data-urlencode "result_statuses=certified" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"short_title": "Supervisor, D8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10,
"measure_code": null,
"description": null,
"ballot_text": null,
"full_text_url": null,
"unopposed": false,
"ballot_order": 41,
"party": null,
"details": {
"nonpartisan": true
},
"source_url": "https://www.sf.gov/departments/department-elections",
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"type": "council",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
}
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"precincts_reporting": 41,
"precincts_total": 41,
"source": {
"name": "San Francisco Department of Elections",
"url": "https://www.sf.gov/departments/department-elections/results",
"contest_name": "Member, Board of Supervisors, District 8",
"updated_at": "2026-11-12T17:00:00-08:00"
},
"official_url": null,
"more_sources": [],
"write_in_votes": 212,
"blank_votes": null,
"over_votes": null,
"under_votes": null,
"other_choices": [
{
"label": "Write-in",
"votes": 212,
"write_in": true
}
],
"rcv_rounds": [
{
"round": 1,
"tallies": {
"Jane Doe": 24133,
"Sam Rivera": 21907,
"Lee Park": 6166
},
"eliminated": ["Lee Park"]
},
{
"round": 2,
"tallies": {
"Jane Doe": 27310,
"Sam Rivera": 23998
}
}
],
"notes": "Ranked choice: Jane Doe leads after round 2.",
"fetched_at": "2026-11-13T06:30:12Z"
},
"choices": [
{
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"incumbent": true,
"vote_meaning": null,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
},
{
"id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"option": null,
"ballot_designation": "Small Business Owner",
"withdrawn": false,
"incumbent": false,
"vote_meaning": null,
"candidate": {
"id": 8812,
"full_name": "Sam Rivera",
"website_url": null,
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 21907,
"vote_pct": 41.793,
"winner": null
}
}
]
},
{
"id": 4502,
"title": "Proposition K: Affordable Housing Bond",
"short_title": "Prop K",
"kind": "measure",
"vote_method": "yes_no",
"votes_allowed": 1,
"ranks_allowed": null,
"measure_code": "K",
"description": "Affordable housing bond.",
"ballot_text": "Shall the City and County of San Francisco issue $300,000,000 in general obligation bonds to build and repair affordable housing?",
"full_text_url": "https://www.sf.gov/departments/department-elections/prop-k-full-text",
"unopposed": false,
"ballot_order": 88,
"party": null,
"details": {
"measure_type": "bond",
"vote_required": "2/3",
"amount": 300000000
},
"source_url": "https://www.sf.gov/departments/department-elections",
"district": {
"id": 70,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco",
"name": "San Francisco",
"level": "county",
"type": "county",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
}
},
"result": {
"status": "certified",
"source_kind": "official",
"party_reported": false,
"total_votes": 389104,
"precincts_reporting": 517,
"precincts_total": 517,
"source": {
"name": "San Francisco Department of Elections",
"url": "https://www.sf.gov/departments/department-elections/results",
"contest_name": "Proposition K",
"updated_at": "2026-12-01T12:00:00-08:00"
},
"official_url": null,
"more_sources": [],
"write_in_votes": null,
"blank_votes": null,
"over_votes": null,
"under_votes": null,
"other_choices": [],
"rcv_rounds": null,
"notes": null,
"fetched_at": "2026-11-13T06:30:12Z"
},
"choices": [
{
"id": 21050,
"label": "Yes",
"measure_answer": "yes",
"option": null,
"ballot_designation": null,
"withdrawn": false,
"incumbent": null,
"vote_meaning": "A Yes vote means the City may issue the bonds.",
"candidate": null,
"party": null,
"party_label": null,
"result": {
"votes": 264590,
"vote_pct": 68,
"winner": true
}
},
{
"id": 21051,
"label": "No",
"measure_answer": "no",
"option": null,
"ballot_designation": null,
"withdrawn": false,
"incumbent": null,
"vote_meaning": "A No vote means the City may not issue the bonds.",
"candidate": null,
"party": null,
"party_label": null,
"result": {
"votes": 124514,
"vote_pct": 32,
"winner": false
}
}
]
}
],
"next_cursor": "aTo0NTAy"
}Get a contest with endorsements
GET /v1/contests/{id}
One contest with its choices, its result once a source has one (votes and winners per choice, the source’s URL, ranked-choice rounds and write-ins), and every endorser’s position: one pick for vote-for-one, up to N picks for vote-for-N, ranked picks for ranked choice, Yes or No for a measure. Narrow the endorsers with categories, endorsers or endorser_states.
Path parameters
idintegerrequiredContest id (from GET /v1/contests).
At least 1.Example4410
Query parameters
categoriescomma-separated stringsOnly endorsers in these categories.
Examplelabor,editorialendorserscomma-separated stringsOnly these endorsers (slugs).
Examplesf-labor-councilendorser_statescomma-separated stringsOnly endorsers based in these states (USPS codes), or national for endorsers with none.
ExampleCA,nationallangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The contest.
dataContestDetail21 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
short_titlestring or nullShorter title.
kindstringcandidate (an office), measure (a proposition) or retention (keep a judge?).
One ofcandidate,measure,retention.vote_methodstringHow votes are cast: plurality (vote for one), multi_seat (vote for up to votes_allowed), ranked_choice (rank up to ranks_allowed), approval, or yes_no.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullHow many choices a voter may mark (vote for N).
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
measure_codestring or nullThe measure’s letter or number: “K”, “Prop 1”.
descriptionstring or nullA short description or question. For a measure with ballot_text, often a summary.
ballot_textstring or nullA measure’s wording exactly as printed on the ballot; null where we don’t have it yet.
full_text_urlstring (uri) or nullThe official full text of a measure.
unopposedbooleanA candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.
ballot_orderinteger or nullPosition on the printed ballot.
partyParty or nullFor a party primary, the party.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
detailsobjectFacts about the contest. Only keys that apply are present.
18 fields
uncontestedbooleanOnly one candidate is running.
nonpartisanbooleanCandidates appear without a party.
measure_typestringbond, charter_amendment, initiative, referendum, tax, …
vote_requiredstringShare of the vote needed to pass, e.g. "2/3" or "55%".
majority_requiredbooleanA candidate needs a majority to win outright (else a runoff).
amountnumberDollar amount of a bond or tax measure.
termstringLength of the term, e.g. "4 years".
short_termbooleanThe seat is for the remainder of an unexpired term.
justicestringThe judge or justice a retention question is about.
runoff_datestring (date)Date of a runoff, if one is needed.
stagestringStage of a multi-round contest.
elected_at_largebooleanElected by the whole jurisdiction, not by district.
none_of_thesebooleanThe ballot offers “None of these candidates” (Nevada).
primary_nominatingbooleanThe primary nominates a party’s candidate.
winnersintegerNumber of winners, where it differs from votes_allowed.
ref_typestringKind of referendum.
party_lines_unverifiedbooleanThe candidates are known, but their party lines haven’t been checked against a printed ballot yet.
question_wordingstringnotice where description summarizes an official notice (Ohio) instead of quoting the ballot.
source_urlstring (uri) or nullThe official source the contest was loaded from.
districtDistrictThe district a contest is voted in.
6 fields
idintegerDistrict id. Matches an officeholder’s district_id.
ocd_idstring or nullOpen Civic Data division id, used in districts= filters.
namestringDistrict name.
levelstringLevel of government.
One offederal,state,county,city,school,special.typestringKind of district: statewide, congressional, state_upper, state_lower, county, council, school, …
statestring or nullUSPS state code.
resultContestResult or nullThe election result, once a source has one; null before then.
17 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest: the printed choices plus write-ins. null on a party-reported result.
precincts_reportinginteger or nullPrecincts counted so far, where the source says.
precincts_totalinteger or nullPrecincts in the contest, where the source says.
sourceobjectThe source of the result.
4 fields
namestringWho published the result.
urlstring (uri)Where it was published: the contest’s own results page where the source has one. Every result has one.
contest_namestring or nullThe contest’s name in the source, which may differ from the printed title.
updated_atstring (date-time) or nullWhen the source last updated it, if it says.
official_urlstring (uri) or nullFor a compiled result, the state’s own certified results, where we have them.
more_sourcesarray of objectFurther announcements of the same result (party-reported results that more than one party published).
2 fields
namestring or nullWho published it.
urlstring (uri)Where.
write_in_votesinteger or nullVotes for write-in candidates, where the source counts them.
blank_votesinteger or nullBlank votes, where the source counts them.
over_votesinteger or nullOvervotes, where the source counts them.
under_votesinteger or nullUndervotes, where the source counts them.
other_choicesarray of OtherResultLineLines the source reports that aren’t on the printed ballot, such as named write-ins.
3 fields
labelstring or nullAs the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.
votesinteger or nullVotes for it.
write_inbooleanA write-in candidate.
rcv_roundsarray of object or nullRanked choice: the rounds of the official tabulation, as the source reports them; null for other contests or where we don’t have them.
notesstring or nullAnything else about the result, such as how a ranked-choice winner was decided.
fetched_atstring (date-time)When we last read it from the source.
electionElection10 fields
idintegerElection id.
slugstringElection slug, used in elections= filters: <state>-<date>-<type>.
namestringElection name.
short_namestring or nullShorter name.
datestring (date)Election day.
statestring or nullState; null for a multi-state election.
typestringKind of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.primary_systemstring or nullHow the state runs primaries.
One ofnone,closed,semi_closed,open,top_two,nonpartisan.info_urlstring (uri) or nullOfficial election page.
detailsobjectVoting rules for the election. Only keys that apply are present.
12 fields
all_mailbooleanEvery voter is mailed a ballot.
ranked_choicebooleanSome contests in this election use ranked choice voting.
fusion_votingbooleanCandidates can appear on more than one party’s line (New York, Connecticut).
partisan_primarybooleanA party primary.
primarystringWhich primary, where a state holds several.
consolidated_withstringElections held on the same ballot.
runoff_datestring (date)Date of a runoff, if one is needed.
runoff_election_datestring (date)Date of the runoff election.
top_two_primary_datestring (date)Date of the top-two primary that chose these candidates.
louisiana_open_primarybooleanLouisiana’s open (jungle) primary.
notestringAnything else voters should know.
historicalbooleanA past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.
choicesarray of ChoiceEvery choice, in ballot order.
12 fields
idintegerChoice id: the choice_id endorsements refer to.
labelstringAs printed: the candidate’s ballot name, or Yes / No.
measure_answerstring or nullyes or no for a measure or retention question; null for a candidate.
One ofyes,no.optionstring or nullA special line: none_of_these (Nevada).
One ofnone_of_these.ballot_designationstring or nullOccupation line printed under the name, e.g. “Transit Planner”.
withdrawnbooleanThe candidate withdrew but is still printed on the ballot.
incumbentboolean or nullThe candidate holds this seat now: true or false where we know, null where we don’t yet (and for a measure answer).
vote_meaningstring or nullFor a measure answer: what a Yes (or No) vote means, as the election office words it.
candidateCandidate or nullThe candidate; null for a measure answer.
4 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
partyParty or nullThe party (or party preference) printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs (fusion lines, top-two preferences).
resultChoiceResult or nullThis choice’s votes, once the contest has a result; null before then.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
endorsementsarray of PositionEvery (matching) endorser’s position, by endorser name.
2 fields
endorserEndorserRefWho made an endorsement.
5 fields
idintegerEndorser id.
slugstringEndorser slug, used in endorsers= filters.
namestringEndorser name.
categoryCategoryThe kind of endorser.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
statestring or nullHome state; null for national endorsers.
picksarray of PickOne pick for vote-for-one, up to N for vote-for-N, the ranking for ranked choice, Yes or No for a measure.
9 fields
endorsement_idintegerThe endorsement’s id (GET /v1/endorsements/{id}).
choice_idintegerThe Choice endorsed.
labelstringThat choice’s label.
measure_answerstring or nullyes or no for a measure.
One ofyes,no.rankinteger or nullRanked choice: 1 for first choice, 2 for second, …; null otherwise.
statementstring or nullWhat the endorser said about it.
source_urlstring (uri) or nullWhere the endorsement was published.
published_onstring (date) or nullWhen it was published.
added_bystringHow the endorsement got here: automatic (added by our automatic watch of the endorser’s own page), staff (entered by hand by BallotData staff) or data_load (loaded in a researched data pass).
One ofautomatic,staff,data_load.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/contests/4410?categories=labor,editorial" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/contests/4410" \
--data-urlencode "categories=labor,editorial" \
--data-urlencode "endorsers=sf-labor-council" \
--data-urlencode "endorser_states=CA,national" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"short_title": "Supervisor, D8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10,
"measure_code": null,
"description": null,
"ballot_text": null,
"full_text_url": null,
"unopposed": false,
"ballot_order": 41,
"party": null,
"details": {
"nonpartisan": true
},
"source_url": "https://www.sf.gov/departments/department-elections",
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"type": "council",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
}
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"precincts_reporting": 41,
"precincts_total": 41,
"source": {
"name": "San Francisco Department of Elections",
"url": "https://www.sf.gov/departments/department-elections/results",
"contest_name": "Member, Board of Supervisors, District 8",
"updated_at": "2026-11-12T17:00:00-08:00"
},
"official_url": null,
"more_sources": [],
"write_in_votes": 212,
"blank_votes": null,
"over_votes": null,
"under_votes": null,
"other_choices": [
{
"label": "Write-in",
"votes": 212,
"write_in": true
}
],
"rcv_rounds": [
{
"round": 1,
"tallies": {
"Jane Doe": 24133,
"Sam Rivera": 21907,
"Lee Park": 6166
},
"eliminated": ["Lee Park"]
},
{
"round": 2,
"tallies": {
"Jane Doe": 27310,
"Sam Rivera": 23998
}
}
],
"notes": "Ranked choice: Jane Doe leads after round 2.",
"fetched_at": "2026-11-13T06:30:12Z"
},
"choices": [
{
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"incumbent": true,
"vote_meaning": null,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
},
{
"id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"option": null,
"ballot_designation": "Small Business Owner",
"withdrawn": false,
"incumbent": false,
"vote_meaning": null,
"candidate": {
"id": 8812,
"full_name": "Sam Rivera",
"website_url": null,
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 21907,
"vote_pct": 41.793,
"winner": null
}
}
],
"endorsements": [
{
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"picks": [
{
"endorsement_id": 90211,
"choice_id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"added_by": "data_load"
},
{
"endorsement_id": 90212,
"choice_id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"added_by": "data_load"
}
]
},
{
"endorser": {
"id": 88,
"slug": "sf-chronicle",
"name": "San Francisco Chronicle",
"category": {
"slug": "editorial",
"name": "Voter guides & editorial boards"
},
"state": "CA"
},
"picks": [
{
"endorsement_id": 90377,
"choice_id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"rank": null,
"statement": "Rivera has the clearer plan for the district’s corridors.",
"source_url": "https://www.sfchronicle.com/opinion/endorsements",
"published_on": "2026-10-02",
"added_by": "automatic"
}
]
}
]
}
}Endorsers
Who endorses: unions, newspapers, parties, clubs, advocacy groups and officials, by category and home state.
List endorser categories
GET /v1/endorser-categories
The kinds of endorser (labor, editorial, environment, business, party, club, community, officials), with how many endorsers each has. Use the slugs in categories= filters.
Query parameters
langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 Every category, in display order.
dataarray of EndorserCategoryEvery item.
4 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name (plural).
singular_namestringOne of them: “Labor union”.
endorser_countintegerEndorsers in this category.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorser-categories" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/endorser-categories" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"slug": "editorial",
"name": "Voter guides & editorial boards",
"singular_name": "Voter guide",
"endorser_count": 214
},
{
"slug": "labor",
"name": "Labor",
"singular_name": "Labor union",
"endorser_count": 391
},
{
"slug": "environment",
"name": "Environment & transportation",
"singular_name": "Environmental group",
"endorser_count": 167
}
]
}List endorsers
GET /v1/endorsers
Endorsers by category, home state, or the elections they’ve endorsed in, or search by name. Every endorser we’ve found is here, however few endorsements it has made; min_endorsements leaves out the smallest.
Query parameters
categoriescomma-separated stringsCategory slugs.
Examplelaborstatescomma-separated stringsHome states (USPS codes), or national.
ExampleCAelectionscomma-separated stringsOnly endorsers with endorsements in these elections (slugs).
Exampleca-2026-11-03-generalqstringName contains (case-insensitive, up to 100 characters).
Examplelabor councilmin_endorsementsintegerOnly endorsers with at least this many contests (endorsement_count). endorsedby.org lists endorsers with 5 or more by default and finds the rest by search; pass 5 to match.
0 to 100000.Example5langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–500.
Default100.1 to 500.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of endorsers, by id.
dataarray of EndorserThis page.
10 fields
idintegerEndorser id.
slugstringEndorser slug, used in endorsers= filters and GET /v1/endorsers/{slug}.
namestringEndorser name.
categoryCategoryThe kind of endorser.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
descriptionstring or nullAbout the endorser.
website_urlstring (uri) or nullEndorser’s website.
logo_urlstring (uri) or nullLogo image.
statestring or nullHome state; null for national endorsers.
partyParty or nullThe party, for a party or party club.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
endorsement_countintegerContests this endorser takes a position in, across every election you can read. A ranked-choice order or a vote-for-N slate counts once.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorsers?categories=labor&states=CA&q=labor+council" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/endorsers" \
--data-urlencode "categories=labor" \
--data-urlencode "states=CA" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "q=labor council" \
--data-urlencode "min_endorsements=5" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"description": "The central labor council for San Francisco, representing over 150 unions.",
"website_url": "https://sflaborcouncil.org",
"logo_url": null,
"state": "CA",
"party": null,
"endorsement_count": 84
}
],
"next_cursor": "aTo1MTI"
}Get an endorser
GET /v1/endorsers/{slug}
One endorser, with how many contests they’ve taken a position in, and the elections they’ve endorsed in with how many contests in each.
Path parameters
slugstringrequiredEndorser slug (from GET /v1/endorsers).
Examplesf-labor-council
Query parameters
langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The endorser.
dataEndorserDetail11 fields
idintegerEndorser id.
slugstringEndorser slug.
namestringEndorser name.
categoryCategoryThe kind of endorser.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
descriptionstring or nullAbout the endorser.
website_urlstring (uri) or nullEndorser’s website.
logo_urlstring (uri) or nullLogo image.
statestring or nullHome state; null for national endorsers.
partyParty or nullThe party, for a party or party club.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
endorsement_countintegerContests this endorser takes a position in, across every election you can read. A ranked-choice order or a vote-for-N slate counts once.
electionsarray of objectElections the endorser has endorsed in, newest first.
4 fields
electionstringElection slug.
namestringElection name.
datestring (date)Election day.
contestsintegerContests this endorser took a position in.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorsers/sf-labor-council" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/endorsers/sf-labor-council" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"description": "The central labor council for San Francisco, representing over 150 unions.",
"website_url": "https://sflaborcouncil.org",
"logo_url": null,
"state": "CA",
"party": null,
"endorsement_count": 84,
"elections": [
{
"election": "ca-2026-11-03-general",
"name": "California General Election",
"date": "2026-11-03",
"contests": 61
},
{
"election": "ca-2026-06-02-primary",
"name": "California Primary Election",
"date": "2026-06-02",
"contests": 23
}
]
}
}Endorsements
Who each endorser backs in each race, and every change to those endorsements.
List endorsements
GET /v1/endorsements
Endorsements, one per endorser and choice, with the basics of each: the endorser, contest, election and choice it’s for. A ranked-choice endorsement of three candidates is three rows with ranks 1–3. This is the way to sync everything, or with changed_since just what’s new since your last sync. GET /v1/endorsements/{id} has the full record, and GET /v1/contests/{id} every position in a contest.
Query parameters
categoriescomma-separated stringsEndorser category slugs (GET /v1/endorser-categories).
Examplelaborendorserscomma-separated stringsEndorser slugs (GET /v1/endorsers).
Examplesf-labor-councilendorser_statescomma-separated stringsEndorsers’ home states (USPS codes), or national for endorsers with none.
ExampleCA,nationalelectionscomma-separated stringsElection slugs (GET /v1/elections).
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAcontest_kindscomma-separated stringsContest kinds: candidate (an office), measure, or retention (whether to keep a judge).
One ofcandidate,measure,retention.Examplemeasurelevelscomma-separated stringsLevels of the contest’s district, from federal down to school and special districts.
One offederal,state,county,city,school,special.Examplecounty,citycontestintegerOnly this contest.
At least 1.Example4410changed_sincestring (date-time)Only endorsements added or changed at or after this time (ISO 8601). Use the time you last synced to fetch just what’s new; deletions aren’t listed here, so check GET /v1/endorsements/{id}/history for ids you hold that stop appearing.
Example2026-11-01T00:00:00Zupcomingbooleantrue for only elections from yesterday on; false (the default) for every election.
Defaultfalse.Exampletruelangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–500.
Default100.1 to 500.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of endorsements, by id.
dataarray of EndorsementThis page.
8 fields
idintegerEndorsement id: GET /v1/endorsements/{id} has the full record.
rankinteger or nullRanked choice: the rank the endorser gave; null otherwise.
published_onstring (date) or nullWhen it was published.
added_bystringHow the endorsement got here: automatic (added by our automatic watch of the endorser’s own page), staff (entered by hand by BallotData staff) or data_load (loaded in a researched data pass).
One ofautomatic,staff,data_load.endorserobjectWho made the endorsement.
3 fields
idintegerEndorser id.
slugstringEndorser slug.
namestringEndorser name.
contestobjectThe contest.
2 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
electionobjectThe contest’s election.
3 fields
idintegerElection id.
slugstringElection slug.
datestring (date)Election day.
choiceobjectThe choice endorsed.
3 fields
idintegerChoice id.
labelstringAs printed: the candidate’s ballot name, or Yes / No.
measure_answerstring or nullyes or no for a measure; null for a candidate.
One ofyes,no.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorsements?elections=ca-2026-11-03-general&categories=labor" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/endorsements" \
--data-urlencode "categories=labor" \
--data-urlencode "endorsers=sf-labor-council" \
--data-urlencode "endorser_states=CA,national" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "states=CA" \
--data-urlencode "contest_kinds=measure" \
--data-urlencode "levels=county,city" \
--data-urlencode "contest=4410" \
--data-urlencode "changed_since=2026-11-01T00:00:00Z" \
--data-urlencode "upcoming=true" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 90211,
"rank": 1,
"published_on": "2026-09-18",
"added_by": "data_load",
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council"
},
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"date": "2026-11-03"
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"measure_answer": null
}
}
],
"next_cursor": "aTo5MDIxMQ"
}Get an endorsement
GET /v1/endorsements/{id}
One endorsement in full: what the endorser said and where, the endorser, the contest with its district, election and voting rules, and the choice with its candidate and party.
Path parameters
idintegerrequiredEndorsement id (from GET /v1/endorsements).
At least 1.Example90211
Query parameters
langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The endorsement.
dataEndorsementDetailAn endorsement in full.
10 fields
idintegerEndorsement id.
rankinteger or nullRanked choice: the rank the endorser gave; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
retrieved_atstring (date-time)When we collected it.
added_bystringHow the endorsement got here: automatic (added by our automatic watch of the endorser’s own page), staff (entered by hand by BallotData staff) or data_load (loaded in a researched data pass).
One ofautomatic,staff,data_load.endorserobjectWho made the endorsement.
9 fields
idintegerEndorser id.
slugstringEndorser slug.
namestringEndorser name.
categoryCategoryThe kind of endorser.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
descriptionstring or nullAbout the endorser.
website_urlstring (uri) or nullEndorser’s website.
logo_urlstring (uri) or nullLogo image.
statestring or nullHome state; null for national endorsers.
partyParty or nullThe party, for a party or party club.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
contestContestBase19 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
short_titlestring or nullShorter title.
kindstringcandidate (an office), measure (a proposition) or retention (keep a judge?).
One ofcandidate,measure,retention.vote_methodstringHow votes are cast: plurality (vote for one), multi_seat (vote for up to votes_allowed), ranked_choice (rank up to ranks_allowed), approval, or yes_no.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullHow many choices a voter may mark (vote for N).
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
measure_codestring or nullThe measure’s letter or number: “K”, “Prop 1”.
descriptionstring or nullA short description or question. For a measure with ballot_text, often a summary.
ballot_textstring or nullA measure’s wording exactly as printed on the ballot; null where we don’t have it yet.
full_text_urlstring (uri) or nullThe official full text of a measure.
unopposedbooleanA candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.
ballot_orderinteger or nullPosition on the printed ballot.
partyParty or nullFor a party primary, the party.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
detailsobjectFacts about the contest. Only keys that apply are present.
18 fields
uncontestedbooleanOnly one candidate is running.
nonpartisanbooleanCandidates appear without a party.
measure_typestringbond, charter_amendment, initiative, referendum, tax, …
vote_requiredstringShare of the vote needed to pass, e.g. "2/3" or "55%".
majority_requiredbooleanA candidate needs a majority to win outright (else a runoff).
amountnumberDollar amount of a bond or tax measure.
termstringLength of the term, e.g. "4 years".
short_termbooleanThe seat is for the remainder of an unexpired term.
justicestringThe judge or justice a retention question is about.
runoff_datestring (date)Date of a runoff, if one is needed.
stagestringStage of a multi-round contest.
elected_at_largebooleanElected by the whole jurisdiction, not by district.
none_of_thesebooleanThe ballot offers “None of these candidates” (Nevada).
primary_nominatingbooleanThe primary nominates a party’s candidate.
winnersintegerNumber of winners, where it differs from votes_allowed.
ref_typestringKind of referendum.
party_lines_unverifiedbooleanThe candidates are known, but their party lines haven’t been checked against a printed ballot yet.
question_wordingstringnotice where description summarizes an official notice (Ohio) instead of quoting the ballot.
source_urlstring (uri) or nullThe official source the contest was loaded from.
districtDistrictThe district a contest is voted in.
6 fields
idintegerDistrict id. Matches an officeholder’s district_id.
ocd_idstring or nullOpen Civic Data division id, used in districts= filters.
namestringDistrict name.
levelstringLevel of government.
One offederal,state,county,city,school,special.typestringKind of district: statewide, congressional, state_upper, state_lower, county, council, school, …
statestring or nullUSPS state code.
resultContestResult or nullThe election result, once a source has one; null before then.
17 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest: the printed choices plus write-ins. null on a party-reported result.
precincts_reportinginteger or nullPrecincts counted so far, where the source says.
precincts_totalinteger or nullPrecincts in the contest, where the source says.
sourceobjectThe source of the result.
4 fields
namestringWho published the result.
urlstring (uri)Where it was published: the contest’s own results page where the source has one. Every result has one.
contest_namestring or nullThe contest’s name in the source, which may differ from the printed title.
updated_atstring (date-time) or nullWhen the source last updated it, if it says.
official_urlstring (uri) or nullFor a compiled result, the state’s own certified results, where we have them.
more_sourcesarray of objectFurther announcements of the same result (party-reported results that more than one party published).
2 fields
namestring or nullWho published it.
urlstring (uri)Where.
write_in_votesinteger or nullVotes for write-in candidates, where the source counts them.
blank_votesinteger or nullBlank votes, where the source counts them.
over_votesinteger or nullOvervotes, where the source counts them.
under_votesinteger or nullUndervotes, where the source counts them.
other_choicesarray of OtherResultLineLines the source reports that aren’t on the printed ballot, such as named write-ins.
3 fields
labelstring or nullAs the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.
votesinteger or nullVotes for it.
write_inbooleanA write-in candidate.
rcv_roundsarray of object or nullRanked choice: the rounds of the official tabulation, as the source reports them; null for other contests or where we don’t have them.
notesstring or nullAnything else about the result, such as how a ranked-choice winner was decided.
fetched_atstring (date-time)When we last read it from the source.
electionElection10 fields
idintegerElection id.
slugstringElection slug, used in elections= filters: <state>-<date>-<type>.
namestringElection name.
short_namestring or nullShorter name.
datestring (date)Election day.
statestring or nullState; null for a multi-state election.
typestringKind of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.primary_systemstring or nullHow the state runs primaries.
One ofnone,closed,semi_closed,open,top_two,nonpartisan.info_urlstring (uri) or nullOfficial election page.
detailsobjectVoting rules for the election. Only keys that apply are present.
12 fields
all_mailbooleanEvery voter is mailed a ballot.
ranked_choicebooleanSome contests in this election use ranked choice voting.
fusion_votingbooleanCandidates can appear on more than one party’s line (New York, Connecticut).
partisan_primarybooleanA party primary.
primarystringWhich primary, where a state holds several.
consolidated_withstringElections held on the same ballot.
runoff_datestring (date)Date of a runoff, if one is needed.
runoff_election_datestring (date)Date of the runoff election.
top_two_primary_datestring (date)Date of the top-two primary that chose these candidates.
louisiana_open_primarybooleanLouisiana’s open (jungle) primary.
notestringAnything else voters should know.
historicalbooleanA past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.
choiceChoiceA line a voter can mark: a candidate, or a Yes/No answer.
12 fields
idintegerChoice id: the choice_id endorsements refer to.
labelstringAs printed: the candidate’s ballot name, or Yes / No.
measure_answerstring or nullyes or no for a measure or retention question; null for a candidate.
One ofyes,no.optionstring or nullA special line: none_of_these (Nevada).
One ofnone_of_these.ballot_designationstring or nullOccupation line printed under the name, e.g. “Transit Planner”.
withdrawnbooleanThe candidate withdrew but is still printed on the ballot.
incumbentboolean or nullThe candidate holds this seat now: true or false where we know, null where we don’t yet (and for a measure answer).
vote_meaningstring or nullFor a measure answer: what a Yes (or No) vote means, as the election office words it.
candidateCandidate or nullThe candidate; null for a measure answer.
4 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
partyParty or nullThe party (or party preference) printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs (fusion lines, top-two preferences).
resultChoiceResult or nullThis choice’s votes, once the contest has a result; null before then.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorsements/90211" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/endorsements/90211" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"id": 90211,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"retrieved_at": "2026-09-19T04:12:08.331Z",
"added_by": "data_load",
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA",
"description": "The central labor council for San Francisco, representing over 150 unions.",
"website_url": "https://sflaborcouncil.org",
"logo_url": null,
"party": null
},
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"short_title": "Supervisor, D8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10,
"measure_code": null,
"description": null,
"ballot_text": null,
"full_text_url": null,
"unopposed": false,
"ballot_order": 41,
"party": null,
"details": {
"nonpartisan": true
},
"source_url": "https://www.sf.gov/departments/department-elections",
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"type": "council",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"short_name": "General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general",
"primary_system": "top_two",
"info_url": "https://www.sos.ca.gov/elections/upcoming-elections",
"details": {
"all_mail": true
}
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"precincts_reporting": 41,
"precincts_total": 41,
"source": {
"name": "San Francisco Department of Elections",
"url": "https://www.sf.gov/departments/department-elections/results",
"contest_name": "Member, Board of Supervisors, District 8",
"updated_at": "2026-11-12T17:00:00-08:00"
},
"official_url": null,
"more_sources": [],
"write_in_votes": 212,
"blank_votes": null,
"over_votes": null,
"under_votes": null,
"other_choices": [
{
"label": "Write-in",
"votes": 212,
"write_in": true
}
],
"rcv_rounds": [
{
"round": 1,
"tallies": {
"Jane Doe": 24133,
"Sam Rivera": 21907,
"Lee Park": 6166
},
"eliminated": ["Lee Park"]
},
{
"round": 2,
"tallies": {
"Jane Doe": 27310,
"Sam Rivera": 23998
}
}
],
"notes": "Ranked choice: Jane Doe leads after round 2.",
"fetched_at": "2026-11-13T06:30:12Z"
}
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"incumbent": true,
"vote_meaning": null,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
}
}
}Get an endorsement’s history
GET /v1/endorsements/{id}/history
Every change to one endorsement, oldest first: when it was added, each change to its choice, rank, statement, source or date with the values before and after, and its deletion. Works after an endorsement is deleted too, so you can tell why an id stopped appearing.
Path parameters
idintegerrequiredEndorsement id (from GET /v1/endorsements, or one you stored earlier).
At least 1.Example90211
Returns
200 Every change, oldest first.
dataarray of HistoryEntryEvery item.
5 fields
atstring (date-time)When the change was made. For endorsements collected before we kept history (October 27, 2026), created is when we collected it.
changestringWhat happened.
One ofcreated,updated,deleted.changedarray of stringFor updated: the fields that changed. Empty otherwise.
One ofchoice_id,rank,statement,source_url,published_on.beforeHistoryValues or nullThe values before the change; null for created.
6 fields
choice_idintegerThe choice endorsed.
choice_labelstring or nullThat choice’s label as printed; null if the choice has since been removed from the ballot.
rankinteger or nullRanked choice: the rank; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
afterHistoryValues or nullThe values after the change; null for deleted.
6 fields
choice_idintegerThe choice endorsed.
choice_labelstring or nullThat choice’s label as printed; null if the choice has since been removed from the ballot.
rankinteger or nullRanked choice: the rank; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/endorsements/90211/history" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"at": "2026-09-19T04:12:08.331Z",
"change": "created",
"changed": [],
"before": null,
"after": {
"choice_id": 20931,
"choice_label": "Jane Doe",
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
}
},
{
"at": "2026-10-03T16:40:51.020Z",
"change": "updated",
"changed": ["rank"],
"before": {
"choice_id": 20931,
"choice_label": "Jane Doe",
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
},
"after": {
"choice_id": 20931,
"choice_label": "Jane Doe",
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
}
}
]
}List changes since a time
GET /v1/changes
Every endorsement added, changed or deleted since a time, in the order they happened, deletions included: the way to keep a copy current without the stream. Each change has the values before and after. Start with since= (the time of your last sync); page on with the cursor. The cursor follows the order changes were made, so changes made while you page show up on later pages, never missed.
Query parameters
sincestring (date-time)Changes at or after this time (ISO 8601). Required unless you pass a cursor.
Example2026-11-01T00:00:00Zcategoriescomma-separated stringsEndorser category slugs (GET /v1/endorser-categories).
Examplelaborendorserscomma-separated stringsEndorser slugs (GET /v1/endorsers).
Examplesf-labor-councilelectionscomma-separated stringsElection slugs (GET /v1/elections).
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAlimitintegerResults per page, 1–1000.
Default100.1 to 1000.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of changes, oldest first.
dataarray of ChangeThis page.
10 fields
idintegerThe change’s id: increasing and unique. Pages are in this order.
atstring (date-time)When the change was made.
changestringWhat happened.
One ofcreated,updated,deleted.changedarray of stringFor updated: the fields that changed. Empty otherwise.
One ofchoice_id,rank,statement,source_url,published_on.endorsement_idintegerThe endorsement changed.
endorserobject4 fields
idintegerEndorser id.
slugstring or nullEndorser slug; null if the endorser has since been removed.
namestring or nullEndorser name; null if the endorser has since been removed.
categoryCategory or nullCategory.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
contestobject2 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
electionobject3 fields
idintegerElection id.
slugstringElection slug.
datestring (date)Election day.
beforeHistoryValues or nullThe values before the change; null for created.
6 fields
choice_idintegerThe choice endorsed.
choice_labelstring or nullThat choice’s label as printed; null if the choice has since been removed from the ballot.
rankinteger or nullRanked choice: the rank; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
afterHistoryValues or nullThe values after the change; null for deleted.
6 fields
choice_idintegerThe choice endorsed.
choice_labelstring or nullThat choice’s label as printed; null if the choice has since been removed from the ballot.
rankinteger or nullRanked choice: the rank; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/changes?since=2026-11-01T00%3A00%3A00Z&states=CA" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/changes" \
--data-urlencode "since=2026-11-01T00:00:00Z" \
--data-urlencode "categories=labor" \
--data-urlencode "endorsers=sf-labor-council" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "states=CA" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 512330,
"at": "2026-11-02T18:04:11.902Z",
"change": "updated",
"changed": ["rank"],
"endorsement_id": 90211,
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
}
},
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"date": "2026-11-03"
},
"before": {
"choice_id": 20931,
"choice_label": "Jane Doe",
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
},
"after": {
"choice_id": 20931,
"choice_label": "Jane Doe",
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
}
},
{
"id": 512331,
"at": "2026-11-02T18:04:11.902Z",
"change": "deleted",
"changed": [],
"endorsement_id": 90377,
"endorser": {
"id": 88,
"slug": "sf-chronicle",
"name": "San Francisco Chronicle",
"category": {
"slug": "editorial",
"name": "Voter guides & editorial boards"
}
},
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"date": "2026-11-03"
},
"before": {
"choice_id": 20932,
"choice_label": "Sam Rivera",
"rank": null,
"statement": null,
"source_url": "https://www.sfchronicle.com/opinion/endorsements",
"published_on": "2026-10-02"
},
"after": null
}
],
"next_cursor": "aTo1MTIzMzE"
}Candidates
The people on the ballot, every endorsement each one received, and where they stand on common issues.
List candidates
GET /v1/candidates
Candidates, each with the contests they’re on and how many endorsers backed them in each. Search by name, or narrow by state, election or incumbency. GET /v1/candidates/{id} has the endorsements themselves.
Query parameters
qstringName contains (case-insensitive, up to 100 characters).
Exampledoestatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAelectionscomma-separated stringsElection slugs (GET /v1/elections).
Exampleca-2026-11-03-generalincumbentbooleantrue for only incumbents, false for only candidates known not to hold the seat. Leave it out for everyone.
Exampletrueupcomingbooleantrue for only elections from yesterday on; false (the default) for every election.
Defaultfalse.Exampletruelangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–500.
Default100.1 to 500.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of candidates, by id.
dataarray of CandidateSummaryThis page.
6 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
incumbentboolean or nullHolds the seat now: true or false where we know, null where we don’t yet.
contestsarray of CandidateContestThe contests they’re on that you can read, newest election first.
6 fields
contestobject6 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
kindstringcandidate or retention.
One ofcandidate,measure,retention.vote_methodstringHow votes are cast.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullVote for N.
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
districtobject5 fields
idintegerDistrict id.
ocd_idstring or nullOCD division id.
namestringDistrict name.
levelstringLevel.
One offederal,state,county,city,school,special.statestring or nullUSPS state code.
electionobject4 fields
idintegerElection id.
slugstringElection slug.
namestringElection name.
datestring (date)Election day.
choiceobject7 fields
idintegerChoice id.
labelstringThe name as printed.
ballot_designationstring or nullOccupation line printed under the name.
withdrawnbooleanWithdrew but still printed.
partyParty or nullThe party printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs.
resultChoiceResult or nullThe candidate’s votes and whether they won, once the contest has a result.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
resultContestResultBrief or nullThe contest’s result, once a source has one.
5 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest. null on a party-reported result.
source_urlstring (uri)Where the result was published.
endorsement_countintegerEndorsers who endorsed this candidate in this contest.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/candidates?q=doe&states=CA" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/candidates" \
--data-urlencode "q=doe" \
--data-urlencode "states=CA" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "incumbent=true" \
--data-urlencode "upcoming=true" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null,
"incumbent": true,
"contests": [
{
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10
},
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"date": "2026-11-03"
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"ballot_designation": "Transit Planner",
"withdrawn": false,
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"source_url": "https://www.sf.gov/departments/department-elections/results"
},
"endorsement_count": 14
}
]
}
],
"next_cursor": "aTo4ODEx"
}Get a candidate with endorsements
GET /v1/candidates/{id}
One candidate, with every contest they’re on, how each turned out once it has a result, and every endorsement they received in each: who, the rank for ranked choice, what they said and where.
Path parameters
idintegerrequiredCandidate id (from GET /v1/candidates, or a choice’s candidate.id).
At least 1.Example8811
Query parameters
langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The candidate.
dataCandidateDetail7 fields
idintegerCandidate id.
full_namestringFull name.
website_urlstring (uri) or nullCampaign website.
photo_urlstring (uri) or nullPhoto.
incumbentboolean or nullHolds the seat now: true or false where we know, null where we don’t yet.
contestsarray of objectThe contests they’re on that you can read, newest election first, each with the endorsements received.
7 fields
contestobject6 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
kindstringcandidate or retention.
One ofcandidate,measure,retention.vote_methodstringHow votes are cast.
One ofplurality,multi_seat,ranked_choice,approval,yes_no.votes_allowedinteger or nullVote for N.
ranks_allowedinteger or nullRanked choice: how many choices a voter may rank.
districtobject5 fields
idintegerDistrict id.
ocd_idstring or nullOCD division id.
namestringDistrict name.
levelstringLevel.
One offederal,state,county,city,school,special.statestring or nullUSPS state code.
electionobject4 fields
idintegerElection id.
slugstringElection slug.
namestringElection name.
datestring (date)Election day.
choiceobject7 fields
idintegerChoice id.
labelstringThe name as printed.
ballot_designationstring or nullOccupation line printed under the name.
withdrawnbooleanWithdrew but still printed.
partyParty or nullThe party printed beside the name.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullThe party text exactly as printed, where it differs.
resultChoiceResult or nullThe candidate’s votes and whether they won, once the contest has a result.
3 fields
votesinteger or nullVotes for this choice. null only on a party-reported result, which names a winner without counts.
vote_pctnumber or nullShare of the contest’s votes, 0 to 100, as the source gives it (three decimals).
winnerboolean or nulltrue for a winner (for vote-for-N, each of the N), false for a choice that lost; null when not decided yet or not stated, such as a race headed to a runoff.
resultContestResultBrief or nullThe contest’s result, once a source has one.
5 fields
statusstringunofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).
One ofunofficial,certified.source_kindstringWhere the result comes from: official (a state, county, city or federal election office), compiled (certified official returns compiled by a research group, such as the MIT Election Data and Science Lab’s precinct returns, summed to the contest) or party (a state or national party’s own announcement of the winner, with no vote counts; replaced as soon as an official result appears).
One ofofficial,compiled,party.party_reportedbooleantrue when the result is a party’s announcement (source_kind party), not an official count.
total_votesinteger or nullVotes cast in the contest. null on a party-reported result.
source_urlstring (uri)Where the result was published.
endorsement_countintegerEndorsers who endorsed this candidate in this contest.
endorsementsarray of CandidateEndorsementEvery endorsement they received in this contest, by endorser name.
7 fields
endorsement_idintegerEndorsement id.
endorserEndorserRefWho made an endorsement.
5 fields
idintegerEndorser id.
slugstringEndorser slug, used in endorsers= filters.
namestringEndorser name.
categoryCategoryThe kind of endorser.
statestring or nullHome state; null for national endorsers.
rankinteger or nullRanked choice: the rank the endorser gave; null otherwise.
statementstring or nullWhat the endorser said.
source_urlstring (uri) or nullWhere it was published.
published_onstring (date) or nullWhen it was published.
added_bystringHow the endorsement got here: automatic (added by our automatic watch of the endorser’s own page), staff (entered by hand by BallotData staff) or data_load (loaded in a researched data pass).
One ofautomatic,staff,data_load.
issue_positionsarray of IssuePositionWhere they stand on common issues (GET /v1/issues), in issue order, each with its sources. Empty where we have none yet.
7 fields
idintegerPosition id.
issueobject2 fields
slugstringIssue slug.
namestringIssue name.
stancestring or nullThe answer to the issue’s question: supports (yes), opposes (no), mixed (yes to part, or yes with limits the summary spells out), or null for a stated position that doesn’t answer it either way.
One ofsupports,opposes,mixed.summarystringWhere they stand, in neutral words: what they said, or how they voted.
quotestring or nullTheir own words, verbatim, where a source has them.
checked_onstring (date)When the sources were last read.
sourcesarray of IssuePositionSourceEvery link the position was taken from; at least one.
5 fields
urlstring (uri)Where the position comes from.
kindstringWhat kind of source: the candidate’s own site, an official roll-call vote record, an official statement, a published candidate questionnaire, a debate, or news reporting.
One ofcandidate_website,voting_record,official_statement,questionnaire,debate,news.publisherstringWho published it: “U.S. Senate”, “The Atlanta Journal-Constitution”.
titlestring or nullIts title.
published_onstring (date) or nullWhen it was published, where known.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/candidates/8811" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/candidates/8811" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null,
"incumbent": true,
"contests": [
{
"contest": {
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"kind": "candidate",
"vote_method": "ranked_choice",
"votes_allowed": 1,
"ranks_allowed": 10
},
"district": {
"id": 77,
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"name": "San Francisco Supervisorial District 8",
"level": "county",
"state": "CA"
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"date": "2026-11-03"
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"ballot_designation": "Transit Planner",
"withdrawn": false,
"party": null,
"party_label": null,
"result": {
"votes": 24133,
"vote_pct": 46.039,
"winner": null
}
},
"result": {
"status": "unofficial",
"source_kind": "official",
"party_reported": false,
"total_votes": 52418,
"source_url": "https://www.sf.gov/departments/department-elections/results"
},
"endorsement_count": 14,
"endorsements": [
{
"endorsement_id": 90211,
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"added_by": "data_load"
}
]
}
],
"issue_positions": [
{
"id": 502,
"issue": {
"slug": "abortion",
"name": "Abortion"
},
"stance": "supports",
"summary": "Says abortion should stay legal and supports writing protections into state law.",
"quote": "Decisions about pregnancy belong to patients and their doctors.",
"checked_on": "2026-11-12",
"sources": [
{
"url": "https://example.com/janedoe/issues",
"kind": "candidate_website",
"publisher": "Campaign website",
"title": "Issues",
"published_on": null
},
{
"url": "https://example.com/news/supervisor-questionnaire",
"kind": "questionnaire",
"publisher": "Example News",
"title": "District 8 candidate questionnaire",
"published_on": "2026-09-30"
}
]
}
]
}
}List issues
GET /v1/issues
The policy issues we record candidates’ positions on (abortion, guns, immigration, schools, the safety net and more), each phrased as a yes/no question that a position’s stance answers. Use the slugs in issues= filters.
Returns
200 Every issue, in display order.
dataarray of IssueEvery item.
4 fields
slugstringIssue slug, for issues= filters.
namestringShort name: “Abortion”.
questionstringThe yes/no question a position’s stance answers.
descriptionstringWhat the issue covers.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/issues" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"slug": "abortion",
"name": "Abortion",
"question": "Should abortion be legal in most or all cases?",
"description": "Access to abortion, including bans, gestational limits, exceptions, federal protections and medication abortion."
},
{
"slug": "guns",
"name": "Gun laws",
"question": "Should gun laws be made stricter?",
"description": "Background checks, assault-weapon and magazine limits, red-flag laws, permit and carry rules."
}
]
}List issue positions
GET /v1/issue-positions
Where candidates stand on common issues, one row per candidate and issue, each with a neutral summary, the candidate’s own words where we have them, and the links it came from: their own site and statements, debates, questionnaires, reputable news, and official roll-call votes for members of Congress. Narrow by issue, stance, candidate, contest, election or state. GET /v1/candidates/{id} has one candidate’s positions with everything else about them.
Query parameters
issuescomma-separated stringsIssue slugs (GET /v1/issues).
Exampleabortion,gunsstancescomma-separated stringsStances: supports, opposes or mixed.
One ofsupports,opposes,mixed.Examplesupportscandidatescomma-separated integersCandidate ids.
Example8811contestscomma-separated integersContest ids: positions of the candidates on these contests.
Example4410electionscomma-separated stringsElection slugs (GET /v1/elections).
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAlimitintegerResults per page, 1–1000.
Default100.1 to 1000.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of positions, by id.
dataarray of IssuePositionItemThis page.
9 fields
idintegerPosition id.
issueobject2 fields
slugstringIssue slug.
namestringIssue name.
stancestring or nullThe answer to the issue’s question: supports (yes), opposes (no), mixed (yes to part, or yes with limits the summary spells out), or null for a stated position that doesn’t answer it either way.
One ofsupports,opposes,mixed.summarystringWhere they stand, in neutral words: what they said, or how they voted.
quotestring or nullTheir own words, verbatim, where a source has them.
checked_onstring (date)When the sources were last read.
sourcesarray of IssuePositionSourceEvery link the position was taken from; at least one.
5 fields
urlstring (uri)Where the position comes from.
kindstringWhat kind of source: the candidate’s own site, an official roll-call vote record, an official statement, a published candidate questionnaire, a debate, or news reporting.
One ofcandidate_website,voting_record,official_statement,questionnaire,debate,news.publisherstringWho published it: “U.S. Senate”, “The Atlanta Journal-Constitution”.
titlestring or nullIts title.
published_onstring (date) or nullWhen it was published, where known.
candidateobject2 fields
idintegerCandidate id.
full_namestringFull name.
contestsarray of objectThe candidate’s contests that you can read (and that match the filters), newest election first.
4 fields
idintegerContest id.
titlestringTitle as printed on the ballot.
electionobject2 fields
slugstringElection slug.
datestring (date)Election day.
statestringUSPS state code.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/issue-positions?issues=abortion&states=CA" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/issue-positions" \
--data-urlencode "issues=abortion,guns" \
--data-urlencode "stances=supports" \
--data-urlencode "candidates=8811" \
--data-urlencode "contests=4410" \
--data-urlencode "elections=ca-2026-11-03-general" \
--data-urlencode "states=CA" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 502,
"issue": {
"slug": "abortion",
"name": "Abortion"
},
"stance": "supports",
"summary": "Says abortion should stay legal and supports writing protections into state law.",
"quote": "Decisions about pregnancy belong to patients and their doctors.",
"checked_on": "2026-11-12",
"sources": [
{
"url": "https://example.com/janedoe/issues",
"kind": "candidate_website",
"publisher": "Campaign website",
"title": "Issues",
"published_on": null
},
{
"url": "https://example.com/news/supervisor-questionnaire",
"kind": "questionnaire",
"publisher": "Example News",
"title": "District 8 candidate questionnaire",
"published_on": "2026-09-30"
}
],
"candidate": {
"id": 8811,
"full_name": "Jane Doe"
},
"contests": [
{
"id": 4410,
"title": "Member, Board of Supervisors, District 8",
"election": {
"slug": "ca-2026-11-03-general",
"date": "2026-11-03"
},
"state": "CA"
}
]
}
],
"next_cursor": "aTo1MDI"
}Officeholders
Who holds each seat now, from Open States.
List officeholders
GET /v1/officeholders
People in office now, with party, term and contact details. Filter by state, office, level or seat (OCD id). Federal and state offices, and the D.C. Council.
Query parameters
statescomma-separated stringsUSPS codes.
ExampleCAofficescomma-separated stringsOffices held.
One ofus_senator,us_representative,governor,lieutenant_governor,attorney_general,secretary_of_state,chief_election_officer,treasurer,auditor,state_senator,state_representative,state_legislator,city_council.Exampleus_senator,governorlevelscomma-separated stringsLevels of office: federal, state, or city (the D.C. Council).
One offederal,state,city.Examplefederaldistrictscomma-separated stringsOCD division ids of the seats.
Exampleocd-division/country:us/state:ca/sldl:17qstringName contains (case-insensitive, up to 100 characters).
Examplemorganlangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.ExampleeslimitintegerResults per page, 1–500.
Default100.1 to 500.Example20cursorstringWhere the next page starts: the next_cursor from the previous page. Leave it out for the first page; it is opaque, so pass it back exactly as you got it.
ExampleaTo0NDEw
Returns
200 A page of officeholders.
dataarray of OfficeholderThis page.
23 fields
person_idstring (uuid)Open States person id; stays the same across terms and offices.
namestringFull name.
given_namestring or nullGiven name.
family_namestring or nullFamily name.
officestringThe office.
One ofus_senator,us_representative,governor,lieutenant_governor,attorney_general,secretary_of_state,chief_election_officer,treasurer,auditor,state_senator,state_representative,state_legislator,city_council.office_titlestringTitle the state uses: “Assemblymember”, “Delegate”.
levelstringLevel of government.
One offederal,state,city.statestringUSPS state code.
districtstring or nullDistrict as the legislature names it: "12", "1A", "21st Middlesex".
district_ocd_idstringOCD division id of the seat.
district_idinteger or nullThe matching contest district id, where there is one.
partystring or nullParty as published: “Democratic”, “Republican/Conservative”.
party_abbreviationstring or nullParty abbreviation.
term_startstring (date) or nullStart of the current term.
term_endstring (date) or nullEnd of the current term.
emailstring (email) or nullOffice email.
phonestring or nullOffice phone (capitol office first).
website_urlstring (uri) or nullOfficial page.
image_urlstring (uri) or nullOfficial photo.
officesarray of objectOffices with their addresses and phones.
4 fields
classificationstringcapitol, district, …
addressstringMailing address.
voicestringPhone.
faxstringFax.
socialobjectSocial accounts, where known.
4 fields
twitterstringHandle.
facebookstringHandle.
instagramstringHandle.
youtubestringHandle.
source_urlsarray of string (uri)Where the data came from.
updated_atstring (date-time)Last refreshed.
next_cursorstring or nullPass as cursor= for the next page; null on the last page.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/officeholders?states=CA&offices=state_representative" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/officeholders" \
--data-urlencode "states=CA" \
--data-urlencode "offices=us_senator,governor" \
--data-urlencode "levels=federal" \
--data-urlencode "districts=ocd-division/country:us/state:ca/sldl:17" \
--data-urlencode "q=morgan" \
--data-urlencode "lang=es" \
--data-urlencode "limit=20" \
--data-urlencode "cursor=aTo0NDEw" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"person_id": "4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",
"name": "Alex Morgan",
"given_name": "Alex",
"family_name": "Morgan",
"office": "state_representative",
"office_title": "Assemblymember",
"level": "state",
"state": "CA",
"district": "17",
"district_ocd_id": "ocd-division/country:us/state:ca/sldl:17",
"district_id": 1203,
"party": "Democratic",
"party_abbreviation": "DEM",
"term_start": "2024-12-02",
"term_end": "2026-11-30",
"email": "assemblymember.morgan@example.gov",
"phone": "916-555-0117",
"website_url": "https://example.gov/morgan",
"image_url": null,
"offices": [
{
"classification": "capitol",
"address": "1021 O Street, Sacramento, CA 95814",
"voice": "916-555-0117"
}
],
"social": {
"twitter": "AsmMorgan"
},
"source_urls": ["https://www.assembly.ca.gov/assemblymembers"],
"updated_at": "2026-10-12T08:00:00Z"
}
],
"next_cursor": "aToxMjA0"
}Get an officeholder
GET /v1/officeholders/{id}
One officeholder by person_id, with every seat they hold.
Path parameters
idstring (uuid)requiredThe officeholder’s person_id (from GET /v1/officeholders).
Example4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b
Query parameters
langstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.Examplees
Returns
200 The officeholder.
dataOfficeholderDetail24 fields
person_idstring (uuid)Open States person id; stays the same across terms and offices.
namestringFull name.
given_namestring or nullGiven name.
family_namestring or nullFamily name.
officestringThe office.
One ofus_senator,us_representative,governor,lieutenant_governor,attorney_general,secretary_of_state,chief_election_officer,treasurer,auditor,state_senator,state_representative,state_legislator,city_council.office_titlestringTitle the state uses: “Assemblymember”, “Delegate”.
levelstringLevel of government.
One offederal,state,city.statestringUSPS state code.
districtstring or nullDistrict as the legislature names it: "12", "1A", "21st Middlesex".
district_ocd_idstringOCD division id of the seat.
district_idinteger or nullThe matching contest district id, where there is one.
partystring or nullParty as published: “Democratic”, “Republican/Conservative”.
party_abbreviationstring or nullParty abbreviation.
term_startstring (date) or nullStart of the current term.
term_endstring (date) or nullEnd of the current term.
emailstring (email) or nullOffice email.
phonestring or nullOffice phone (capitol office first).
website_urlstring (uri) or nullOfficial page.
image_urlstring (uri) or nullOfficial photo.
officesarray of objectOffices with their addresses and phones.
4 fields
classificationstringcapitol, district, …
addressstringMailing address.
voicestringPhone.
faxstringFax.
socialobjectSocial accounts, where known.
4 fields
twitterstringHandle.
facebookstringHandle.
instagramstringHandle.
youtubestringHandle.
source_urlsarray of string (uri)Where the data came from.
updated_atstring (date-time)Last refreshed.
seatsarray of OfficeholderEvery seat they hold (rarely more than one).
23 fields
person_idstring (uuid)Open States person id; stays the same across terms and offices.
namestringFull name.
given_namestring or nullGiven name.
family_namestring or nullFamily name.
officestringThe office.
One ofus_senator,us_representative,governor,lieutenant_governor,attorney_general,secretary_of_state,chief_election_officer,treasurer,auditor,state_senator,state_representative,state_legislator,city_council.office_titlestringTitle the state uses: “Assemblymember”, “Delegate”.
levelstringLevel of government.
One offederal,state,city.statestringUSPS state code.
districtstring or nullDistrict as the legislature names it: "12", "1A", "21st Middlesex".
district_ocd_idstringOCD division id of the seat.
district_idinteger or nullThe matching contest district id, where there is one.
partystring or nullParty as published: “Democratic”, “Republican/Conservative”.
party_abbreviationstring or nullParty abbreviation.
term_startstring (date) or nullStart of the current term.
term_endstring (date) or nullEnd of the current term.
emailstring (email) or nullOffice email.
phonestring or nullOffice phone (capitol office first).
website_urlstring (uri) or nullOfficial page.
image_urlstring (uri) or nullOfficial photo.
officesarray of objectOffices with their addresses and phones.
4 fields
classificationstringcapitol, district, …
addressstringMailing address.
voicestringPhone.
faxstringFax.
socialobjectSocial accounts, where known.
4 fields
twitterstringHandle.
facebookstringHandle.
instagramstringHandle.
youtubestringHandle.
source_urlsarray of string (uri)Where the data came from.
updated_atstring (date-time)Last refreshed.
Errors
400A parameter is missing or not valid.401No API key, or one that isn’t valid, was revoked or expired.403The key can’t be used for this: a missing scope, an IP allowlist, an account not yet approved, or data the account hasn’t bought.404Nothing has that id or slug.429Over a rate limit: your account’s requests a minute or a day (UTC), its open streams, or too many requests with a missing or wrong key from your address. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl "https://api.ballotdata.ai/v1/officeholders/4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl -G "https://api.ballotdata.ai/v1/officeholders/4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b" \
--data-urlencode "lang=es" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"person_id": "4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",
"name": "Alex Morgan",
"given_name": "Alex",
"family_name": "Morgan",
"office": "state_representative",
"office_title": "Assemblymember",
"level": "state",
"state": "CA",
"district": "17",
"district_ocd_id": "ocd-division/country:us/state:ca/sldl:17",
"district_id": 1203,
"party": "Democratic",
"party_abbreviation": "DEM",
"term_start": "2024-12-02",
"term_end": "2026-11-30",
"email": "assemblymember.morgan@example.gov",
"phone": "916-555-0117",
"website_url": "https://example.gov/morgan",
"image_url": null,
"offices": [
{
"classification": "capitol",
"address": "1021 O Street, Sacramento, CA 95814",
"voice": "916-555-0117"
}
],
"social": {
"twitter": "AsmMorgan"
},
"source_urls": ["https://www.assembly.ca.gov/assemblymembers"],
"updated_at": "2026-10-12T08:00:00Z",
"seats": [
{
"person_id": "4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",
"name": "Alex Morgan",
"given_name": "Alex",
"family_name": "Morgan",
"office": "state_representative",
"office_title": "Assemblymember",
"level": "state",
"state": "CA",
"district": "17",
"district_ocd_id": "ocd-division/country:us/state:ca/sldl:17",
"district_id": 1203,
"party": "Democratic",
"party_abbreviation": "DEM",
"term_start": "2024-12-02",
"term_end": "2026-11-30",
"email": "assemblymember.morgan@example.gov",
"phone": "916-555-0117",
"website_url": "https://example.gov/morgan",
"image_url": null,
"offices": [
{
"classification": "capitol",
"address": "1021 O Street, Sacramento, CA 95814",
"voice": "916-555-0117"
}
],
"social": {
"twitter": "AsmMorgan"
},
"source_urls": ["https://www.assembly.ca.gov/assemblymembers"],
"updated_at": "2026-10-12T08:00:00Z"
}
]
}
}