API reference
U.S. ballots, endorsers and endorsements, and a live stream of endorsement changes.
- Base URL
https://api.ballotdata.net- Authentication
Authorization: Bearer ebk_…on every request (orX-API-Key: ebk_…). Keys come from the console; keep them on your servers.- Format
- JSON over HTTPS. Dates are
YYYY-MM-DD; times are ISO 8601 in UTC. - OpenAPI
- openapi.json (OpenAPI 3.1.0, version 1.1.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.net/v1/endorsements?states=CA&categories=labor" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"curl "https://api.ballotdata.net/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 60 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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/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 and endorsements each has. Use an election’s slug to ask for its contests or endorsements.
Query parameters
statescomma-separated stringsUSPS codes.
ExampleCAtypescomma-separated stringsKinds of election.
One ofgeneral,primary,special,special_primary,runoff,consolidated.ExamplegeneralupcomingbooleanOnly elections from yesterday on.
Defaultfalse.fromstring (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.limitintegerResults per page, 1–500.
Default100.1 to 500.cursorstringThe next_cursor from the previous page.
Returns
200 A page of elections, by id.
dataarray of ElectionSummaryThis page.
12 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.
11 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.
contest_countintegerContests on the ballot.
endorsement_countintegerEndorsements in those contests.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/elections?states=CA&upcoming=true" \
-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
}
],
"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
upcomingbooleanCount only upcoming elections.
Defaulttrue.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/coverage" \
-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"
}
]
}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.
List contests
GET /v1/contests
The races on the ballot, each with its district, its election, its voting rules and every choice in ballot order. Filter by election, state, level, kind or district. To see who endorsed whom, use GET /v1/contests/{id} or /v1/endorsements.
Query parameters
electionscomma-separated stringsElection slugs.
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAlevelscomma-separated stringsDistrict levels.
One offederal,state,county,city,school,special.Examplecounty,citycontest_kindscomma-separated stringsContest kinds.
One ofcandidate,measure,retention.Examplemeasuredistrictscomma-separated stringsOCD division ids of the districts.
Exampleocd-division/country:us/state:ca/county:san_franciscoupcomingbooleanOnly elections from yesterday on.
Defaultfalse.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.limitintegerResults per page, 1–200.
Default50.1 to 200.cursorstringThe next_cursor from the previous page.
Returns
200 A page of contests, by id.
dataarray of ContestThis page.
16 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 nullMeasure text or question, where the ballot prints one.
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.
16 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.
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.
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.
11 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.
choicesarray of ChoiceEvery choice, in ballot order.
9 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.
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).
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/contests?elections=ca-2026-11-03-general&levels=county&limit=20" \
-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_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
}
},
"choices": [
{
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null
},
{
"id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"option": null,
"ballot_designation": "Small Business Owner",
"withdrawn": false,
"candidate": {
"id": 8812,
"full_name": "Sam Rivera",
"website_url": null,
"photo_url": null
},
"party": null,
"party_label": null
}
]
}
],
"next_cursor": "aTo0NDEw"
}Get a contest with endorsements
GET /v1/contests/{id}
One contest with its choices 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.
At least 1.Example4410
Query parameters
categoriescomma-separated stringsOnly endorsers in these categories.
Examplelabor,editorialendorserscomma-separated stringsOnly these endorsers.
endorser_statescomma-separated stringsOnly endorsers based in these states, or national.
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.
Returns
200 The contest.
dataContestDetail17 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 nullMeasure text or question, where the ballot prints one.
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.
16 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.
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.
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.
11 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.
choicesarray of ChoiceEvery choice, in ballot order.
9 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.
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).
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.
7 fields
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.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/contests/4410?categories=labor,editorial" \
-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_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
}
},
"choices": [
{
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"ballot_designation": "Transit Planner",
"withdrawn": false,
"candidate": {
"id": 8811,
"full_name": "Jane Doe",
"website_url": "https://example.com/janedoe",
"photo_url": null
},
"party": null,
"party_label": null
},
{
"id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"option": null,
"ballot_designation": "Small Business Owner",
"withdrawn": false,
"candidate": {
"id": 8812,
"full_name": "Sam Rivera",
"website_url": null,
"photo_url": null
},
"party": null,
"party_label": null
}
],
"endorsements": [
{
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"picks": [
{
"choice_id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
},
{
"choice_id": 20932,
"label": "Sam Rivera",
"measure_answer": null,
"rank": 2,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
}
]
},
{
"endorser": {
"id": 88,
"slug": "sf-chronicle",
"name": "San Francisco Chronicle",
"category": {
"slug": "editorial",
"name": "Voter guides & editorial boards"
},
"state": "CA"
},
"picks": [
{
"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"
}
]
}
]
}
}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.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/endorser-categories" \
-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.
Query parameters
categoriescomma-separated stringsCategory slugs.
Examplelaborstatescomma-separated stringsHome states (USPS codes), or national.
ExampleCAelectionscomma-separated stringsOnly endorsers with endorsements in these elections.
qstringName contains (case-insensitive, up to 100 characters).
Examplelabor councillangstringes for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.
One ofen,es.Defaulten.limitintegerResults per page, 1–500.
Default100.1 to 500.cursorstringThe next_cursor from the previous page.
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_countintegerEndorsements this endorser has made, in every election.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/endorsers?categories=labor&states=CA&q=labor+council" \
-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 the elections they’ve endorsed in and how many contests in each.
Path parameters
slugstringrequiredEndorser slug.
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.
Returns
200 The endorser.
dataEndorserDetail10 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.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/endorsers/sf-labor-council" \
-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,
"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.
List endorsements
GET /v1/endorsements
Endorsements, one per endorser and choice, each with its endorser, contest (with district and election) and choice. A ranked-choice endorsement of three candidates is three rows with ranks 1–3. This is the way to sync everything, or to catch up after the stream.
Query parameters
categoriescomma-separated stringsEndorser category slugs (GET /v1/endorser-categories).
Examplelaborendorserscomma-separated stringsEndorser slugs.
Examplesf-labor-councilendorser_statescomma-separated stringsEndorsers’ home states (USPS codes), or national for endorsers with none.
ExampleCA,nationalelectionscomma-separated stringsElection slugs.
Exampleca-2026-11-03-generalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAcontest_kindscomma-separated stringsContest kinds.
One ofcandidate,measure,retention.Examplemeasurelevelscomma-separated stringsDistrict levels.
One offederal,state,county,city,school,special.Examplecounty,citycontestintegerOnly this contest.
At least 1.Example4410upcomingbooleanOnly elections from yesterday on.
Defaultfalse.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.limitintegerResults per page, 1–500.
Default100.1 to 500.cursorstringThe next_cursor from the previous page.
Returns
200 A page of endorsements, by id.
dataarray of EndorsementThis page.
8 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.
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.
contestContestBase15 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 nullMeasure text or question, where the ballot prints one.
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.
16 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.
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.
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.
11 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.
choiceobject8 fields
idintegerChoice id.
labelstringAs printed.
measure_answerstring or nullyes or no for a measure.
One ofyes,no.optionstring or nullnone_of_these, for that line.
One ofnone_of_these.withdrawnbooleanThe candidate withdrew.
candidateobject or nullThe candidate; null for a measure answer.
2 fields
idintegerCandidate id.
full_namestringFull name.
partyParty or nullPrinted party.
3 fields
slugstringParty slug.
namestringParty name.
abbreviationstring or nullShort form as printed, e.g. DEM.
party_labelstring or nullParty text as printed.
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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/endorsements?elections=ca-2026-11-03-general&categories=labor" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 90211,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18",
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"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_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
}
}
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"option": null,
"withdrawn": false,
"candidate": {
"id": 8811,
"full_name": "Jane Doe"
},
"party": null,
"party_label": null
}
}
],
"next_cursor": "aTo5MDIxMQ"
}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.
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.
One offederal,state,city.Examplefederaldistrictscomma-separated stringsOCD division ids of the seats.
Exampleocd-division/country:us/state:ca/sldl:17qstringName contains.
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.limitintegerResults per page, 1–500.
Default100.1 to 500.cursorstringThe next_cursor from the previous page.
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. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/officeholders?states=CA&offices=state_representative" \
-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.
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.
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. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/officeholders/4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b" \
-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"
}
]
}
}Stream
Every endorsement added, changed or removed, as Server-Sent Events or by polling. Needs the stream:read scope and a plan that includes the stream.
Stream endorsement changes (SSE)
GET /v1/stream
Every endorsement change matching a subscription or inline filter, as Server-Sent Events, within seconds of it happening. Events: ready (first, with the subscription name), endorsement (an Event), checkpoint (moves your position past changes your filter skipped), reconnect (we end connections after an hour), and error (cursor_expired). A comment line arrives every 20 seconds.
Each event id is a cursor: reconnect with Last-Event-ID (EventSource does this itself) or ?cursor= to continue. Opening a stream counts as one request; your plan sets how many you can hold open.
Query parameters
subscriptionstringA saved subscription’s id or name, instead of filter parameters.
ExampleCA laborcategoriescomma-separated stringsEndorser category slugs (GET /v1/endorser-categories).
Examplelaborendorserscomma-separated stringsEndorser slugs.
Examplesf-labor-councilendorser_statescomma-separated stringsEndorsers’ home states (USPS codes), or national for endorsers with none.
ExampleCA,nationalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAelectionscomma-separated stringsElection slugs.
Exampleca-2026-11-03-generalcontest_kindscomma-separated stringsContest kinds.
One ofcandidate,measure,retention.Examplemeasurelevelscomma-separated stringsDistrict levels.
One offederal,state,county,city,school,special.Examplecounty,citytypescomma-separated stringsKinds of change.
One ofcreated,updated,deleted.Examplecreated,updatedcursorstringResume after this position: a next_cursor, or an SSE event id. Also read from the Last-Event-ID header.
fromstringWhere to start without a cursor.
One oflatest,earliest.Defaultlatest.sincestring (date-time)Start at changes from this time, within the stream’s retention.
Returns
200 An open event stream.
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.410The cursor is older than the stream keeps.429Over a rate limit. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl -N "https://api.ballotdata.net/v1/stream?subscription=CA%20labor" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"retry: 5000
id: c1.eyJzIjp7IjAiOiIxODM0MSJ9fQ
event: ready
data: {"subscription":"CA labor"}
id: c1.eyJzIjp7IjAiOiIxODM0MiJ9fQ
event: endorsement
data: {"id":"18342","type":"endorsement.created","occurred_at":"2026-10-14T21:07:33.512Z","endorsement":{"id":90211,"rank":1,"statement":null,"source_url":"https://sflaborcouncil.org/endorsements","published_on":"2026-09-18"},"endorser":{"id":512,"slug":"sf-labor-council","name":"San Francisco Labor Council","category":{"slug":"labor","name":"Labor"},"state":"CA"},"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,"district":{"id":77,"name":"San Francisco Supervisorial District 8","level":"county","ocd_id":"ocd-division/country:us/state:ca/county:san_francisco/council_district:8","state":"CA"}},"election":{"id":3,"slug":"ca-2026-11-03-general","name":"California General Election","date":"2026-11-03","state":"CA","type":"general"},"choice":{"id":20931,"label":"Jane Doe","measure_answer":null,"candidate":{"id":8811,"full_name":"Jane Doe"},"party":null,"withdrawn":false}}
: ping
id: c1.eyJzIjp7IjAiOiIxODM1MCJ9fQ
event: checkpoint
data: {}
Poll endorsement changes
GET /v1/events
The same changes as the stream, a page at a time. Store next_cursor and send it next time; wait (up to 25 seconds) holds the request open until a matching change arrives. has_more means call again right away.
Query parameters
subscriptionstringA saved subscription’s id or name, instead of filter parameters.
ExampleCA laborcategoriescomma-separated stringsEndorser category slugs (GET /v1/endorser-categories).
Examplelaborendorserscomma-separated stringsEndorser slugs.
Examplesf-labor-councilendorser_statescomma-separated stringsEndorsers’ home states (USPS codes), or national for endorsers with none.
ExampleCA,nationalstatescomma-separated stringsStates the contests are voted in (USPS codes).
ExampleCAelectionscomma-separated stringsElection slugs.
Exampleca-2026-11-03-generalcontest_kindscomma-separated stringsContest kinds.
One ofcandidate,measure,retention.Examplemeasurelevelscomma-separated stringsDistrict levels.
One offederal,state,county,city,school,special.Examplecounty,citytypescomma-separated stringsKinds of change.
One ofcreated,updated,deleted.Examplecreated,updatedcursorstringResume after this position: a next_cursor, or an SSE event id. Also read from the Last-Event-ID header.
fromstringWhere to start without a cursor.
One oflatest,earliest.Defaultlatest.sincestring (date-time)Start at changes from this time, within the stream’s retention.
limitintegerEvents per page, 1–1000.
Default100.1 to 1000.waitintegerSeconds to wait for a change (long polling).
Default0.0 to 25.Example20
Returns
200 Events and the next cursor.
dataarray of EventChanges, oldest first.
8 fields
idstringThe change’s id: increasing and unique. Use it to drop repeats.
typestringWhat happened.
One ofendorsement.created,endorsement.updated,endorsement.deleted.occurred_atstring (date-time)When the change was made.
endorsementobject5 fields
idintegerEndorsement id.
rankinteger or nullRanked choice rank.
statementstring or nullWhat the endorser said.
source_urlstring or nullWhere it was published.
published_onstring (date) or nullWhen.
endorserobject5 fields
idintegerEndorser id.
slugstring or nullEndorser slug.
namestring or nullEndorser name.
categoryCategory or nullCategory.
2 fields
slugstringCategory slug, used in categories= filters.
namestringDisplay name.
statestring or nullHome state, or null for national.
contestobject9 fields
idintegerContest id.
titlestring or nullTitle.
short_titlestring or nullShort title.
kindstring or nullcandidate, measure or retention.
vote_methodstring or nullVote method.
votes_allowedinteger or nullVote for N.
ranks_allowedinteger or nullRanks allowed.
measure_codestring or nullMeasure code.
districtobject or nullDistrict.
5 fields
idintegerDistrict id.
namestringName.
levelstringLevel.
ocd_idstring or nullOCD id.
statestring or nullState.
electionobject or nullElection.
6 fields
idintegerElection id.
slugstringSlug.
namestringName.
datestring (date)Date.
statestring or nullState.
typestringType.
choiceobject6 fields
idintegerChoice id.
labelstring or nullAs printed.
measure_answerstring or nullyes or no.
One ofyes,no.candidateobject or nullCandidate.
2 fields
idintegerCandidate id.
full_namestringFull name.
partyobject or nullParty.
2 fields
slugstringSlug.
namestringName.
withdrawnboolean or nullWithdrawn.
next_cursorstringSend as cursor= next time.
has_morebooleanMore are waiting: call again now.
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.410The cursor is older than the stream keeps.429Over a rate limit. Wait Retry-After seconds.503Not ready yet on this server. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/events?subscription=CA+labor&cursor=c1.eyJzIjp7IjAiOiIxODM0MSJ9fQ&wait=20" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": "18342",
"type": "endorsement.created",
"occurred_at": "2026-10-14T21:07:33.512Z",
"endorsement": {
"id": 90211,
"rank": 1,
"statement": null,
"source_url": "https://sflaborcouncil.org/endorsements",
"published_on": "2026-09-18"
},
"endorser": {
"id": 512,
"slug": "sf-labor-council",
"name": "San Francisco Labor Council",
"category": {
"slug": "labor",
"name": "Labor"
},
"state": "CA"
},
"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,
"district": {
"id": 77,
"name": "San Francisco Supervisorial District 8",
"level": "county",
"ocd_id": "ocd-division/country:us/state:ca/county:san_francisco/council_district:8",
"state": "CA"
}
},
"election": {
"id": 3,
"slug": "ca-2026-11-03-general",
"name": "California General Election",
"date": "2026-11-03",
"state": "CA",
"type": "general"
},
"choice": {
"id": 20931,
"label": "Jane Doe",
"measure_answer": null,
"candidate": {
"id": 8811,
"full_name": "Jane Doe"
},
"party": null,
"withdrawn": false
}
}
],
"next_cursor": "c1.eyJzIjp7IjAiOiIxODM0MiJ9fQ",
"has_more": false
}Subscriptions
Saved filters for the stream and events. Reading needs stream:read; changing needs subscriptions:write.
List subscriptions
GET /v1/subscriptions
Your account’s saved subscriptions, by name.
Returns
200 Every subscription.
dataarray of SubscriptionEvery item.
5 fields
idintegerSubscription id.
namestringName, unique in your account.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
created_atstring (date-time)Created.
updated_atstring (date-time)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. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/subscriptions" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": [
{
"id": 7,
"name": "CA labor",
"filter": {
"categories": ["labor"],
"states": ["CA"]
},
"created_at": "2026-10-15T17:20:04.112Z",
"updated_at": "2026-10-15T17:20:04.112Z"
}
]
}Create a subscription
POST /v1/subscriptions
Save a named filter. Use it as subscription=<id or name> on /v1/stream and /v1/events. An account can have up to 50.
Request body
JSON.
namestring1–100 letters, digits, spaces, dots, dashes or underscores.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
Returns
201 Created.
dataSubscription5 fields
idintegerSubscription id.
namestringName, unique in your account.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
created_atstring (date-time)Created.
updated_atstring (date-time)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.409A subscription with that name already exists.429Over a rate limit. Wait Retry-After seconds.
curl -X POST "https://api.ballotdata.net/v1/subscriptions" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"CA labor","filter":{"categories":["labor"],"states":["CA"]}}'{
"data": {
"id": 7,
"name": "CA labor",
"filter": {
"categories": ["labor"],
"states": ["CA"]
},
"created_at": "2026-10-15T17:20:04.112Z",
"updated_at": "2026-10-15T17:20:04.112Z"
}
}Get a subscription
GET /v1/subscriptions/{id}
One saved subscription.
Path parameters
idintegerrequiredSubscription id.
Example7
Returns
200 The subscription.
dataSubscription5 fields
idintegerSubscription id.
namestringName, unique in your account.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
created_atstring (date-time)Created.
updated_atstring (date-time)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.404Nothing has that id or slug.429Over a rate limit. Wait Retry-After seconds.
curl "https://api.ballotdata.net/v1/subscriptions/7" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"{
"data": {
"id": 7,
"name": "CA labor",
"filter": {
"categories": ["labor"],
"states": ["CA"]
},
"created_at": "2026-10-15T17:20:04.112Z",
"updated_at": "2026-10-15T17:20:04.112Z"
}
}Replace a subscription
PUT /v1/subscriptions/{id}
Replace a subscription’s name and filter. Open streams using it keep their old filter until they reconnect.
Path parameters
idintegerrequiredSubscription id.
Example7
Request body
JSON.
namestring1–100 letters, digits, spaces, dots, dashes or underscores.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
Returns
200 Saved.
dataSubscription5 fields
idintegerSubscription id.
namestringName, unique in your account.
filterFilterWhich endorsements: fields combine with AND, values within a field with OR, and a missing field matches everything. Each field takes at most 500 values.
8 fields
categoriesarray of stringEndorser category slugs (GET /v1/endorser-categories).
endorsersarray of stringEndorser slugs.
endorser_statesarray of stringEndorsers’ home states (USPS codes), or "national".
statesarray of stringStates the contests are voted in.
electionsarray of stringElection slugs.
contest_kindsarray of stringContest kinds.
One ofcandidate,measure,retention.levelsarray of stringDistrict levels.
One offederal,state,county,city,school,special.typesarray of stringStream only: kinds of change.
One ofcreated,updated,deleted.
created_atstring (date-time)Created.
updated_atstring (date-time)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.404Nothing has that id or slug.409A subscription with that name already exists.429Over a rate limit. Wait Retry-After seconds.
curl -X PUT "https://api.ballotdata.net/v1/subscriptions/7" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"CA labor","filter":{"categories":["labor"],"states":["CA","NV"]}}'{
"data": {
"id": 7,
"name": "CA labor",
"filter": {
"categories": ["labor"],
"states": ["CA", "NV"]
},
"created_at": "2026-10-15T17:20:04.112Z",
"updated_at": "2026-10-21T09:12:40.006Z"
}
}Delete a subscription
DELETE /v1/subscriptions/{id}
Delete a subscription.
Path parameters
idintegerrequiredSubscription id.
Example7
Returns
204 Deleted. No body.
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. Wait Retry-After seconds.
curl -X DELETE "https://api.ballotdata.net/v1/subscriptions/7" \
-H "Authorization: Bearer $BALLOTDATA_API_KEY"(no body)