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 (or X-API-Key: ebk_…). Keys come from the console; keep them on your servers.
Client libraries
npm install ballotdata or pip 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.

Authenticate
curl "https://api.ballotdata.ai/v1/endorsements?states=CA&categories=labor" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Page through results
curl "https://api.ballotdata.ai/v1/endorsements?elections=ca-2026-11-03-general&cursor=aTo5MDIxMQ" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Error429
{
  "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 State

    Every item.

    3 fields
    • codestring

      USPS state code.

    • fipsstring

      Two-digit FIPS code.

    • namestring

      Full name.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/states
curl "https://api.ballotdata.ai/v1/states" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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 strings

    USPS codes.

    Example CA
  • typescomma-separated strings

    Kinds of election (general, primary, special, runoff and so on).

    One of general, primary, special, special_primary, runoff, consolidated.Example general
  • upcomingboolean

    true for only elections from yesterday on; false (the default) for every election.

    Default false.Example true
  • fromstring (date)

    Elections on or after this date.

    Example 2026-11-01
  • tostring (date)

    Elections on or before this date.

    Example 2026-12-31
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–500.

    Default 100.1 to 500.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of elections, by id.

  • dataarray of ElectionSummary

    This page.

    13 fields
    • idinteger

      Election id.

    • slugstring

      Election slug, used in elections= filters: <state>-<date>-<type>.

    • namestring

      Election name.

    • short_namestring or null

      Shorter name.

    • datestring (date)

      Election day.

    • statestring or null

      State; null for a multi-state election.

    • typestring

      Kind of election.

      One of general, primary, special, special_primary, runoff, consolidated.
    • primary_systemstring or null

      How the state runs primaries.

      One of none, closed, semi_closed, open, top_two, nonpartisan.
    • info_urlstring (uri) or null

      Official election page.

    • detailsobject

      Voting rules for the election. Only keys that apply are present.

      12 fields
      • all_mailboolean

        Every voter is mailed a ballot.

      • ranked_choiceboolean

        Some contests in this election use ranked choice voting.

      • fusion_votingboolean

        Candidates can appear on more than one party’s line (New York, Connecticut).

      • partisan_primaryboolean

        A party primary.

      • primarystring

        Which primary, where a state holds several.

      • consolidated_withstring

        Elections 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_primaryboolean

        Louisiana’s open (jungle) primary.

      • notestring

        Anything else voters should know.

      • historicalboolean

        A past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.

    • contest_countinteger

      Contests on the ballot.

    • endorsement_countinteger

      Endorsements in those contests.

    • result_countinteger

      Contests on the ballot with a result.

  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/elections
curl "https://api.ballotdata.ai/v1/elections?states=CA&upcoming=true" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • upcomingboolean

    true (the default) counts only upcoming elections; false counts every election.

    Default true.Example false

Returns

200 One row per state.

  • dataarray of Coverage

    Every item.

    11 fields
    • statestring

      USPS state code.

    • namestring

      State name.

    • election_countinteger

      Elections.

    • contest_countinteger

      Contests.

    • measure_countinteger

      Of those, ballot measures.

    • endorser_countinteger

      Endorsers with endorsements here.

    • endorsement_countinteger

      Endorsements.

    • endorsed_contest_countinteger

      Contests with at least one endorsement.

    • first_electionstring (date) or null

      Earliest election.

    • last_electionstring (date) or null

      Latest election.

    • updated_atstring (date-time) or null

      When a contest or endorsement last changed.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/coverage
curl "https://api.ballotdata.ai/v1/coverage" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/coverage" \
  --data-urlencode "upcoming=false" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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

  • slugstringrequired

    Election slug (from GET /v1/elections).

    Example ca-2026-11-03-general

Query parameters

  • formatstring

    json (the default) or csv.

    One of json, csv.Default json.Example csv
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

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.

  • dataElectionExport
    2 fields
    • electionElection
      10 fields
      • idinteger

        Election id.

      • slugstring

        Election slug, used in elections= filters: <state>-<date>-<type>.

      • namestring

        Election name.

      • short_namestring or null

        Shorter name.

      • datestring (date)

        Election day.

      • statestring or null

        State; null for a multi-state election.

      • typestring

        Kind of election.

        One of general, primary, special, special_primary, runoff, consolidated.
      • primary_systemstring or null

        How the state runs primaries.

        One of none, closed, semi_closed, open, top_two, nonpartisan.
      • info_urlstring (uri) or null

        Official election page.

      • detailsobject

        Voting rules for the election. Only keys that apply are present.

        12 fields
        • all_mailboolean

          Every voter is mailed a ballot.

        • ranked_choiceboolean

          Some contests in this election use ranked choice voting.

        • fusion_votingboolean

          Candidates can appear on more than one party’s line (New York, Connecticut).

        • partisan_primaryboolean

          A party primary.

        • primarystring

          Which primary, where a state holds several.

        • consolidated_withstring

          Elections 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_primaryboolean

          Louisiana’s open (jungle) primary.

        • notestring

          Anything else voters should know.

        • historicalboolean

          A past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.

    • contestsarray of object

      Every contest you can read in the election, in ballot order: races from federal down, then measures.

      20 fields
      • idinteger

        Contest id.

      • titlestring

        Title as printed on the ballot.

      • short_titlestring or null

        Shorter title.

      • kindstring

        candidate (an office), measure (a proposition) or retention (keep a judge?).

        One of candidate, measure, retention.
      • vote_methodstring

        How 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 of plurality, multi_seat, ranked_choice, approval, yes_no.
      • votes_allowedinteger or null

        How many choices a voter may mark (vote for N).

      • ranks_allowedinteger or null

        Ranked choice: how many choices a voter may rank.

      • measure_codestring or null

        The measure’s letter or number: “K”, “Prop 1”.

      • descriptionstring or null

        A short description or question. For a measure with ballot_text, often a summary.

      • ballot_textstring or null

        A measure’s wording exactly as printed on the ballot; null where we don’t have it yet.

      • full_text_urlstring (uri) or null

        The official full text of a measure.

      • unopposedboolean

        A candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.

      • ballot_orderinteger or null

        Position on the printed ballot.

      • partyParty or null

        For a party primary, the party.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

      • detailsobject

        Facts about the contest. Only keys that apply are present.

        18 fields
        • uncontestedboolean

          Only one candidate is running.

        • nonpartisanboolean

          Candidates appear without a party.

        • measure_typestring

          bond, charter_amendment, initiative, referendum, tax, …

        • vote_requiredstring

          Share of the vote needed to pass, e.g. "2/3" or "55%".

        • majority_requiredboolean

          A candidate needs a majority to win outright (else a runoff).

        • amountnumber

          Dollar amount of a bond or tax measure.

        • termstring

          Length of the term, e.g. "4 years".

        • short_termboolean

          The seat is for the remainder of an unexpired term.

        • justicestring

          The judge or justice a retention question is about.

        • runoff_datestring (date)

          Date of a runoff, if one is needed.

        • stagestring

          Stage of a multi-round contest.

        • elected_at_largeboolean

          Elected by the whole jurisdiction, not by district.

        • none_of_theseboolean

          The ballot offers “None of these candidates” (Nevada).

        • primary_nominatingboolean

          The primary nominates a party’s candidate.

        • winnersinteger

          Number of winners, where it differs from votes_allowed.

        • ref_typestring

          Kind of referendum.

        • party_lines_unverifiedboolean

          The candidates are known, but their party lines haven’t been checked against a printed ballot yet.

        • question_wordingstring

          notice where description summarizes an official notice (Ohio) instead of quoting the ballot.

      • source_urlstring (uri) or null

        The official source the contest was loaded from.

      • districtDistrict

        The district a contest is voted in.

        6 fields
        • idinteger

          District id. Matches an officeholder’s district_id.

        • ocd_idstring or null

          Open Civic Data division id, used in districts= filters.

        • namestring

          District name.

        • levelstring

          Level of government.

          One of federal, state, county, city, school, special.
        • typestring

          Kind of district: statewide, congressional, state_upper, state_lower, county, council, school, …

        • statestring or null

          USPS state code.

      • resultContestResult or null

        The election result, once a source has one; null before then.

        17 fields
        • statusstring

          unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

          One of unofficial, certified.
        • source_kindstring

          Where 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 of official, compiled, party.
        • party_reportedboolean

          true when the result is a party’s announcement (source_kind party), not an official count.

        • total_votesinteger or null

          Votes cast in the contest: the printed choices plus write-ins. null on a party-reported result.

        • precincts_reportinginteger or null

          Precincts counted so far, where the source says.

        • precincts_totalinteger or null

          Precincts in the contest, where the source says.

        • sourceobject

          The source of the result.

          4 fields
          • namestring

            Who 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 null

            The contest’s name in the source, which may differ from the printed title.

          • updated_atstring (date-time) or null

            When the source last updated it, if it says.

        • official_urlstring (uri) or null

          For a compiled result, the state’s own certified results, where we have them.

        • more_sourcesarray of object

          Further announcements of the same result (party-reported results that more than one party published).

          2 fields
          • namestring or null

            Who published it.

          • urlstring (uri)

            Where.

        • write_in_votesinteger or null

          Votes for write-in candidates, where the source counts them.

        • blank_votesinteger or null

          Blank votes, where the source counts them.

        • over_votesinteger or null

          Overvotes, where the source counts them.

        • under_votesinteger or null

          Undervotes, where the source counts them.

        • other_choicesarray of OtherResultLine

          Lines the source reports that aren’t on the printed ballot, such as named write-ins.

          3 fields
          • labelstring or null

            As the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.

          • votesinteger or null

            Votes for it.

          • write_inboolean

            A write-in candidate.

        • rcv_roundsarray of object or null

          Ranked 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 null

          Anything 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 Choice

        Every choice, in ballot order.

        12 fields
        • idinteger

          Choice id: the choice_id endorsements refer to.

        • labelstring

          As printed: the candidate’s ballot name, or Yes / No.

        • measure_answerstring or null

          yes or no for a measure or retention question; null for a candidate.

          One of yes, no.
        • optionstring or null

          A special line: none_of_these (Nevada).

          One of none_of_these.
        • ballot_designationstring or null

          Occupation line printed under the name, e.g. “Transit Planner”.

        • withdrawnboolean

          The candidate withdrew but is still printed on the ballot.

        • incumbentboolean or null

          The 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 null

          For a measure answer: what a Yes (or No) vote means, as the election office words it.

        • candidateCandidate or null

          The candidate; null for a measure answer.

          4 fields
          • idinteger

            Candidate id.

          • full_namestring

            Full name.

          • website_urlstring (uri) or null

            Campaign website.

          • photo_urlstring (uri) or null

            Photo.

        • partyParty or null

          The party (or party preference) printed beside the name.

          3 fields
          • slugstring

            Party slug.

          • namestring

            Party name.

          • abbreviationstring or null

            Short form as printed, e.g. DEM.

        • party_labelstring or null

          The party text exactly as printed, where it differs (fusion lines, top-two preferences).

        • resultChoiceResult or null

          This choice’s votes, once the contest has a result; null before then.

          3 fields
          • votesinteger or null

            Votes for this choice. null only on a party-reported result, which names a winner without counts.

          • vote_pctnumber or null

            Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

          • winnerboolean or null

            true 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 Position

        Every endorser’s position, by endorser name.

        2 fields
        • endorserEndorserRef

          Who made an endorsement.

          5 fields
          • idinteger

            Endorser id.

          • slugstring

            Endorser slug, used in endorsers= filters.

          • namestring

            Endorser name.

          • categoryCategory

            The kind of endorser.

          • statestring or null

            Home state; null for national endorsers.

        • picksarray of Pick

          One 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_idinteger

            The endorsement’s id (GET /v1/endorsements/{id}).

          • choice_idinteger

            The Choice endorsed.

          • labelstring

            That choice’s label.

          • measure_answerstring or null

            yes or no for a measure.

            One of yes, no.
          • rankinteger or null

            Ranked choice: 1 for first choice, 2 for second, …; null otherwise.

          • statementstring or null

            What the endorser said about it.

          • source_urlstring (uri) or null

            Where the endorsement was published.

          • published_onstring (date) or null

            When it was published.

          • added_bystring

            How 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 of automatic, staff, data_load.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/elections/{slug}/export
curl "https://api.ballotdata.ai/v1/elections/ca-2026-11-03-general/export?format=csv" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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 strings

    Election slugs (GET /v1/elections).

    Example ca-2026-11-03-general
  • statescomma-separated strings

    States the contests are voted in (USPS codes).

    Example CA
  • levelscomma-separated strings

    Levels of the contest’s district, from federal down to school and special districts.

    One of federal, state, county, city, school, special.Example county,city
  • contest_kindscomma-separated strings

    Contest kinds: candidate (an office), measure, or retention (whether to keep a judge).

    One of candidate, measure, retention.Example measure
  • districtscomma-separated strings

    OCD division ids of the contests’ districts.

    Example ocd-division/country:us/state:ca/county:san_francisco
  • upcomingboolean

    true for only elections from yesterday on; false (the default) for every election.

    Default false.Example true
  • has_resultboolean

    true for only contests with a result; false for only contests without one yet. Leave it out for both.

    Example true
  • result_statusescomma-separated strings

    Only contests whose result has one of these statuses: unofficial (counts still changing) or certified.

    One of unofficial, certified.Example certified
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–200.

    Default 50.1 to 200.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of contests, by id.

  • dataarray of Contest

    This page.

    20 fields
    • idinteger

      Contest id.

    • titlestring

      Title as printed on the ballot.

    • short_titlestring or null

      Shorter title.

    • kindstring

      candidate (an office), measure (a proposition) or retention (keep a judge?).

      One of candidate, measure, retention.
    • vote_methodstring

      How 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 of plurality, multi_seat, ranked_choice, approval, yes_no.
    • votes_allowedinteger or null

      How many choices a voter may mark (vote for N).

    • ranks_allowedinteger or null

      Ranked choice: how many choices a voter may rank.

    • measure_codestring or null

      The measure’s letter or number: “K”, “Prop 1”.

    • descriptionstring or null

      A short description or question. For a measure with ballot_text, often a summary.

    • ballot_textstring or null

      A measure’s wording exactly as printed on the ballot; null where we don’t have it yet.

    • full_text_urlstring (uri) or null

      The official full text of a measure.

    • unopposedboolean

      A candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.

    • ballot_orderinteger or null

      Position on the printed ballot.

    • partyParty or null

      For a party primary, the party.

      3 fields
      • slugstring

        Party slug.

      • namestring

        Party name.

      • abbreviationstring or null

        Short form as printed, e.g. DEM.

    • detailsobject

      Facts about the contest. Only keys that apply are present.

      18 fields
      • uncontestedboolean

        Only one candidate is running.

      • nonpartisanboolean

        Candidates appear without a party.

      • measure_typestring

        bond, charter_amendment, initiative, referendum, tax, …

      • vote_requiredstring

        Share of the vote needed to pass, e.g. "2/3" or "55%".

      • majority_requiredboolean

        A candidate needs a majority to win outright (else a runoff).

      • amountnumber

        Dollar amount of a bond or tax measure.

      • termstring

        Length of the term, e.g. "4 years".

      • short_termboolean

        The seat is for the remainder of an unexpired term.

      • justicestring

        The judge or justice a retention question is about.

      • runoff_datestring (date)

        Date of a runoff, if one is needed.

      • stagestring

        Stage of a multi-round contest.

      • elected_at_largeboolean

        Elected by the whole jurisdiction, not by district.

      • none_of_theseboolean

        The ballot offers “None of these candidates” (Nevada).

      • primary_nominatingboolean

        The primary nominates a party’s candidate.

      • winnersinteger

        Number of winners, where it differs from votes_allowed.

      • ref_typestring

        Kind of referendum.

      • party_lines_unverifiedboolean

        The candidates are known, but their party lines haven’t been checked against a printed ballot yet.

      • question_wordingstring

        notice where description summarizes an official notice (Ohio) instead of quoting the ballot.

    • source_urlstring (uri) or null

      The official source the contest was loaded from.

    • districtDistrict

      The district a contest is voted in.

      6 fields
      • idinteger

        District id. Matches an officeholder’s district_id.

      • ocd_idstring or null

        Open Civic Data division id, used in districts= filters.

      • namestring

        District name.

      • levelstring

        Level of government.

        One of federal, state, county, city, school, special.
      • typestring

        Kind of district: statewide, congressional, state_upper, state_lower, county, council, school, …

      • statestring or null

        USPS state code.

    • resultContestResult or null

      The election result, once a source has one; null before then.

      17 fields
      • statusstring

        unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

        One of unofficial, certified.
      • source_kindstring

        Where 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 of official, compiled, party.
      • party_reportedboolean

        true when the result is a party’s announcement (source_kind party), not an official count.

      • total_votesinteger or null

        Votes cast in the contest: the printed choices plus write-ins. null on a party-reported result.

      • precincts_reportinginteger or null

        Precincts counted so far, where the source says.

      • precincts_totalinteger or null

        Precincts in the contest, where the source says.

      • sourceobject

        The source of the result.

        4 fields
        • namestring

          Who 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 null

          The contest’s name in the source, which may differ from the printed title.

        • updated_atstring (date-time) or null

          When the source last updated it, if it says.

      • official_urlstring (uri) or null

        For a compiled result, the state’s own certified results, where we have them.

      • more_sourcesarray of object

        Further announcements of the same result (party-reported results that more than one party published).

        2 fields
        • namestring or null

          Who published it.

        • urlstring (uri)

          Where.

      • write_in_votesinteger or null

        Votes for write-in candidates, where the source counts them.

      • blank_votesinteger or null

        Blank votes, where the source counts them.

      • over_votesinteger or null

        Overvotes, where the source counts them.

      • under_votesinteger or null

        Undervotes, where the source counts them.

      • other_choicesarray of OtherResultLine

        Lines the source reports that aren’t on the printed ballot, such as named write-ins.

        3 fields
        • labelstring or null

          As the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.

        • votesinteger or null

          Votes for it.

        • write_inboolean

          A write-in candidate.

      • rcv_roundsarray of object or null

        Ranked 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 null

        Anything 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.

    • electionElection
      10 fields
      • idinteger

        Election id.

      • slugstring

        Election slug, used in elections= filters: <state>-<date>-<type>.

      • namestring

        Election name.

      • short_namestring or null

        Shorter name.

      • datestring (date)

        Election day.

      • statestring or null

        State; null for a multi-state election.

      • typestring

        Kind of election.

        One of general, primary, special, special_primary, runoff, consolidated.
      • primary_systemstring or null

        How the state runs primaries.

        One of none, closed, semi_closed, open, top_two, nonpartisan.
      • info_urlstring (uri) or null

        Official election page.

      • detailsobject

        Voting rules for the election. Only keys that apply are present.

        12 fields
        • all_mailboolean

          Every voter is mailed a ballot.

        • ranked_choiceboolean

          Some contests in this election use ranked choice voting.

        • fusion_votingboolean

          Candidates can appear on more than one party’s line (New York, Connecticut).

        • partisan_primaryboolean

          A party primary.

        • primarystring

          Which primary, where a state holds several.

        • consolidated_withstring

          Elections 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_primaryboolean

          Louisiana’s open (jungle) primary.

        • notestring

          Anything else voters should know.

        • historicalboolean

          A past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.

    • choicesarray of Choice

      Every choice, in ballot order.

      12 fields
      • idinteger

        Choice id: the choice_id endorsements refer to.

      • labelstring

        As printed: the candidate’s ballot name, or Yes / No.

      • measure_answerstring or null

        yes or no for a measure or retention question; null for a candidate.

        One of yes, no.
      • optionstring or null

        A special line: none_of_these (Nevada).

        One of none_of_these.
      • ballot_designationstring or null

        Occupation line printed under the name, e.g. “Transit Planner”.

      • withdrawnboolean

        The candidate withdrew but is still printed on the ballot.

      • incumbentboolean or null

        The 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 null

        For a measure answer: what a Yes (or No) vote means, as the election office words it.

      • candidateCandidate or null

        The candidate; null for a measure answer.

        4 fields
        • idinteger

          Candidate id.

        • full_namestring

          Full name.

        • website_urlstring (uri) or null

          Campaign website.

        • photo_urlstring (uri) or null

          Photo.

      • partyParty or null

        The party (or party preference) printed beside the name.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

      • party_labelstring or null

        The party text exactly as printed, where it differs (fusion lines, top-two preferences).

      • resultChoiceResult or null

        This choice’s votes, once the contest has a result; null before then.

        3 fields
        • votesinteger or null

          Votes for this choice. null only on a party-reported result, which names a winner without counts.

        • vote_pctnumber or null

          Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

        • winnerboolean or null

          true 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 null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/contests
curl "https://api.ballotdata.ai/v1/contests?elections=ca-2026-11-03-general&levels=county&limit=20" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • idintegerrequired

    Contest id (from GET /v1/contests).

    At least 1.Example 4410

Query parameters

  • categoriescomma-separated strings

    Only endorsers in these categories.

    Example labor,editorial
  • endorserscomma-separated strings

    Only these endorsers (slugs).

    Example sf-labor-council
  • endorser_statescomma-separated strings

    Only endorsers based in these states (USPS codes), or national for endorsers with none.

    Example CA,national
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 The contest.

  • dataContestDetail
    21 fields
    • idinteger

      Contest id.

    • titlestring

      Title as printed on the ballot.

    • short_titlestring or null

      Shorter title.

    • kindstring

      candidate (an office), measure (a proposition) or retention (keep a judge?).

      One of candidate, measure, retention.
    • vote_methodstring

      How 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 of plurality, multi_seat, ranked_choice, approval, yes_no.
    • votes_allowedinteger or null

      How many choices a voter may mark (vote for N).

    • ranks_allowedinteger or null

      Ranked choice: how many choices a voter may rank.

    • measure_codestring or null

      The measure’s letter or number: “K”, “Prop 1”.

    • descriptionstring or null

      A short description or question. For a measure with ballot_text, often a summary.

    • ballot_textstring or null

      A measure’s wording exactly as printed on the ballot; null where we don’t have it yet.

    • full_text_urlstring (uri) or null

      The official full text of a measure.

    • unopposedboolean

      A candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.

    • ballot_orderinteger or null

      Position on the printed ballot.

    • partyParty or null

      For a party primary, the party.

      3 fields
      • slugstring

        Party slug.

      • namestring

        Party name.

      • abbreviationstring or null

        Short form as printed, e.g. DEM.

    • detailsobject

      Facts about the contest. Only keys that apply are present.

      18 fields
      • uncontestedboolean

        Only one candidate is running.

      • nonpartisanboolean

        Candidates appear without a party.

      • measure_typestring

        bond, charter_amendment, initiative, referendum, tax, …

      • vote_requiredstring

        Share of the vote needed to pass, e.g. "2/3" or "55%".

      • majority_requiredboolean

        A candidate needs a majority to win outright (else a runoff).

      • amountnumber

        Dollar amount of a bond or tax measure.

      • termstring

        Length of the term, e.g. "4 years".

      • short_termboolean

        The seat is for the remainder of an unexpired term.

      • justicestring

        The judge or justice a retention question is about.

      • runoff_datestring (date)

        Date of a runoff, if one is needed.

      • stagestring

        Stage of a multi-round contest.

      • elected_at_largeboolean

        Elected by the whole jurisdiction, not by district.

      • none_of_theseboolean

        The ballot offers “None of these candidates” (Nevada).

      • primary_nominatingboolean

        The primary nominates a party’s candidate.

      • winnersinteger

        Number of winners, where it differs from votes_allowed.

      • ref_typestring

        Kind of referendum.

      • party_lines_unverifiedboolean

        The candidates are known, but their party lines haven’t been checked against a printed ballot yet.

      • question_wordingstring

        notice where description summarizes an official notice (Ohio) instead of quoting the ballot.

    • source_urlstring (uri) or null

      The official source the contest was loaded from.

    • districtDistrict

      The district a contest is voted in.

      6 fields
      • idinteger

        District id. Matches an officeholder’s district_id.

      • ocd_idstring or null

        Open Civic Data division id, used in districts= filters.

      • namestring

        District name.

      • levelstring

        Level of government.

        One of federal, state, county, city, school, special.
      • typestring

        Kind of district: statewide, congressional, state_upper, state_lower, county, council, school, …

      • statestring or null

        USPS state code.

    • resultContestResult or null

      The election result, once a source has one; null before then.

      17 fields
      • statusstring

        unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

        One of unofficial, certified.
      • source_kindstring

        Where 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 of official, compiled, party.
      • party_reportedboolean

        true when the result is a party’s announcement (source_kind party), not an official count.

      • total_votesinteger or null

        Votes cast in the contest: the printed choices plus write-ins. null on a party-reported result.

      • precincts_reportinginteger or null

        Precincts counted so far, where the source says.

      • precincts_totalinteger or null

        Precincts in the contest, where the source says.

      • sourceobject

        The source of the result.

        4 fields
        • namestring

          Who 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 null

          The contest’s name in the source, which may differ from the printed title.

        • updated_atstring (date-time) or null

          When the source last updated it, if it says.

      • official_urlstring (uri) or null

        For a compiled result, the state’s own certified results, where we have them.

      • more_sourcesarray of object

        Further announcements of the same result (party-reported results that more than one party published).

        2 fields
        • namestring or null

          Who published it.

        • urlstring (uri)

          Where.

      • write_in_votesinteger or null

        Votes for write-in candidates, where the source counts them.

      • blank_votesinteger or null

        Blank votes, where the source counts them.

      • over_votesinteger or null

        Overvotes, where the source counts them.

      • under_votesinteger or null

        Undervotes, where the source counts them.

      • other_choicesarray of OtherResultLine

        Lines the source reports that aren’t on the printed ballot, such as named write-ins.

        3 fields
        • labelstring or null

          As the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.

        • votesinteger or null

          Votes for it.

        • write_inboolean

          A write-in candidate.

      • rcv_roundsarray of object or null

        Ranked 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 null

        Anything 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.

    • electionElection
      10 fields
      • idinteger

        Election id.

      • slugstring

        Election slug, used in elections= filters: <state>-<date>-<type>.

      • namestring

        Election name.

      • short_namestring or null

        Shorter name.

      • datestring (date)

        Election day.

      • statestring or null

        State; null for a multi-state election.

      • typestring

        Kind of election.

        One of general, primary, special, special_primary, runoff, consolidated.
      • primary_systemstring or null

        How the state runs primaries.

        One of none, closed, semi_closed, open, top_two, nonpartisan.
      • info_urlstring (uri) or null

        Official election page.

      • detailsobject

        Voting rules for the election. Only keys that apply are present.

        12 fields
        • all_mailboolean

          Every voter is mailed a ballot.

        • ranked_choiceboolean

          Some contests in this election use ranked choice voting.

        • fusion_votingboolean

          Candidates can appear on more than one party’s line (New York, Connecticut).

        • partisan_primaryboolean

          A party primary.

        • primarystring

          Which primary, where a state holds several.

        • consolidated_withstring

          Elections 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_primaryboolean

          Louisiana’s open (jungle) primary.

        • notestring

          Anything else voters should know.

        • historicalboolean

          A past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.

    • choicesarray of Choice

      Every choice, in ballot order.

      12 fields
      • idinteger

        Choice id: the choice_id endorsements refer to.

      • labelstring

        As printed: the candidate’s ballot name, or Yes / No.

      • measure_answerstring or null

        yes or no for a measure or retention question; null for a candidate.

        One of yes, no.
      • optionstring or null

        A special line: none_of_these (Nevada).

        One of none_of_these.
      • ballot_designationstring or null

        Occupation line printed under the name, e.g. “Transit Planner”.

      • withdrawnboolean

        The candidate withdrew but is still printed on the ballot.

      • incumbentboolean or null

        The 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 null

        For a measure answer: what a Yes (or No) vote means, as the election office words it.

      • candidateCandidate or null

        The candidate; null for a measure answer.

        4 fields
        • idinteger

          Candidate id.

        • full_namestring

          Full name.

        • website_urlstring (uri) or null

          Campaign website.

        • photo_urlstring (uri) or null

          Photo.

      • partyParty or null

        The party (or party preference) printed beside the name.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

      • party_labelstring or null

        The party text exactly as printed, where it differs (fusion lines, top-two preferences).

      • resultChoiceResult or null

        This choice’s votes, once the contest has a result; null before then.

        3 fields
        • votesinteger or null

          Votes for this choice. null only on a party-reported result, which names a winner without counts.

        • vote_pctnumber or null

          Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

        • winnerboolean or null

          true 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 Position

      Every (matching) endorser’s position, by endorser name.

      2 fields
      • endorserEndorserRef

        Who made an endorsement.

        5 fields
        • idinteger

          Endorser id.

        • slugstring

          Endorser slug, used in endorsers= filters.

        • namestring

          Endorser name.

        • categoryCategory

          The kind of endorser.

          2 fields
          • slugstring

            Category slug, used in categories= filters.

          • namestring

            Display name.

        • statestring or null

          Home state; null for national endorsers.

      • picksarray of Pick

        One 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_idinteger

          The endorsement’s id (GET /v1/endorsements/{id}).

        • choice_idinteger

          The Choice endorsed.

        • labelstring

          That choice’s label.

        • measure_answerstring or null

          yes or no for a measure.

          One of yes, no.
        • rankinteger or null

          Ranked choice: 1 for first choice, 2 for second, …; null otherwise.

        • statementstring or null

          What the endorser said about it.

        • source_urlstring (uri) or null

          Where the endorsement was published.

        • published_onstring (date) or null

          When it was published.

        • added_bystring

          How 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 of automatic, staff, data_load.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/contests/{id}
curl "https://api.ballotdata.ai/v1/contests/4410?categories=labor,editorial" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 Every category, in display order.

  • dataarray of EndorserCategory

    Every item.

    4 fields
    • slugstring

      Category slug, used in categories= filters.

    • namestring

      Display name (plural).

    • singular_namestring

      One of them: “Labor union”.

    • endorser_countinteger

      Endorsers in this category.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/endorser-categories
curl "https://api.ballotdata.ai/v1/endorser-categories" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/endorser-categories" \
  --data-urlencode "lang=es" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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 strings

    Category slugs.

    Example labor
  • statescomma-separated strings

    Home states (USPS codes), or national.

    Example CA
  • electionscomma-separated strings

    Only endorsers with endorsements in these elections (slugs).

    Example ca-2026-11-03-general
  • qstring

    Name contains (case-insensitive, up to 100 characters).

    Example labor council
  • min_endorsementsinteger

    Only 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.Example 5
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–500.

    Default 100.1 to 500.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of endorsers, by id.

  • dataarray of Endorser

    This page.

    10 fields
    • idinteger

      Endorser id.

    • slugstring

      Endorser slug, used in endorsers= filters and GET /v1/endorsers/{slug}.

    • namestring

      Endorser name.

    • categoryCategory

      The kind of endorser.

      2 fields
      • slugstring

        Category slug, used in categories= filters.

      • namestring

        Display name.

    • descriptionstring or null

      About the endorser.

    • website_urlstring (uri) or null

      Endorser’s website.

    • logo_urlstring (uri) or null

      Logo image.

    • statestring or null

      Home state; null for national endorsers.

    • partyParty or null

      The party, for a party or party club.

      3 fields
      • slugstring

        Party slug.

      • namestring

        Party name.

      • abbreviationstring or null

        Short form as printed, e.g. DEM.

    • endorsement_countinteger

      Contests 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 null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/endorsers
curl "https://api.ballotdata.ai/v1/endorsers?categories=labor&states=CA&q=labor+council" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • slugstringrequired

    Endorser slug (from GET /v1/endorsers).

    Example sf-labor-council

Query parameters

  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 The endorser.

  • dataEndorserDetail
    11 fields
    • idinteger

      Endorser id.

    • slugstring

      Endorser slug.

    • namestring

      Endorser name.

    • categoryCategory

      The kind of endorser.

      2 fields
      • slugstring

        Category slug, used in categories= filters.

      • namestring

        Display name.

    • descriptionstring or null

      About the endorser.

    • website_urlstring (uri) or null

      Endorser’s website.

    • logo_urlstring (uri) or null

      Logo image.

    • statestring or null

      Home state; null for national endorsers.

    • partyParty or null

      The party, for a party or party club.

      3 fields
      • slugstring

        Party slug.

      • namestring

        Party name.

      • abbreviationstring or null

        Short form as printed, e.g. DEM.

    • endorsement_countinteger

      Contests 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 object

      Elections the endorser has endorsed in, newest first.

      4 fields
      • electionstring

        Election slug.

      • namestring

        Election name.

      • datestring (date)

        Election day.

      • contestsinteger

        Contests this endorser took a position in.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/endorsers/{slug}
curl "https://api.ballotdata.ai/v1/endorsers/sf-labor-council" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/endorsers/sf-labor-council" \
  --data-urlencode "lang=es" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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 strings

    Endorser category slugs (GET /v1/endorser-categories).

    Example labor
  • endorserscomma-separated strings

    Endorser slugs (GET /v1/endorsers).

    Example sf-labor-council
  • endorser_statescomma-separated strings

    Endorsers’ home states (USPS codes), or national for endorsers with none.

    Example CA,national
  • electionscomma-separated strings

    Election slugs (GET /v1/elections).

    Example ca-2026-11-03-general
  • statescomma-separated strings

    States the contests are voted in (USPS codes).

    Example CA
  • contest_kindscomma-separated strings

    Contest kinds: candidate (an office), measure, or retention (whether to keep a judge).

    One of candidate, measure, retention.Example measure
  • levelscomma-separated strings

    Levels of the contest’s district, from federal down to school and special districts.

    One of federal, state, county, city, school, special.Example county,city
  • contestinteger

    Only this contest.

    At least 1.Example 4410
  • changed_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.

    Example 2026-11-01T00:00:00Z
  • upcomingboolean

    true for only elections from yesterday on; false (the default) for every election.

    Default false.Example true
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–500.

    Default 100.1 to 500.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of endorsements, by id.

  • dataarray of Endorsement

    This page.

    8 fields
    • idinteger

      Endorsement id: GET /v1/endorsements/{id} has the full record.

    • rankinteger or null

      Ranked choice: the rank the endorser gave; null otherwise.

    • published_onstring (date) or null

      When it was published.

    • added_bystring

      How 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 of automatic, staff, data_load.
    • endorserobject

      Who made the endorsement.

      3 fields
      • idinteger

        Endorser id.

      • slugstring

        Endorser slug.

      • namestring

        Endorser name.

    • contestobject

      The contest.

      2 fields
      • idinteger

        Contest id.

      • titlestring

        Title as printed on the ballot.

    • electionobject

      The contest’s election.

      3 fields
      • idinteger

        Election id.

      • slugstring

        Election slug.

      • datestring (date)

        Election day.

    • choiceobject

      The choice endorsed.

      3 fields
      • idinteger

        Choice id.

      • labelstring

        As printed: the candidate’s ballot name, or Yes / No.

      • measure_answerstring or null

        yes or no for a measure; null for a candidate.

        One of yes, no.
  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/endorsements
curl "https://api.ballotdata.ai/v1/endorsements?elections=ca-2026-11-03-general&categories=labor" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • idintegerrequired

    Endorsement id (from GET /v1/endorsements).

    At least 1.Example 90211

Query parameters

  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 The endorsement.

  • dataEndorsementDetail

    An endorsement in full.

    10 fields
    • idinteger

      Endorsement id.

    • rankinteger or null

      Ranked choice: the rank the endorser gave; null otherwise.

    • statementstring or null

      What the endorser said.

    • source_urlstring (uri) or null

      Where it was published.

    • published_onstring (date) or null

      When it was published.

    • retrieved_atstring (date-time)

      When we collected it.

    • added_bystring

      How 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 of automatic, staff, data_load.
    • endorserobject

      Who made the endorsement.

      9 fields
      • idinteger

        Endorser id.

      • slugstring

        Endorser slug.

      • namestring

        Endorser name.

      • categoryCategory

        The kind of endorser.

        2 fields
        • slugstring

          Category slug, used in categories= filters.

        • namestring

          Display name.

      • descriptionstring or null

        About the endorser.

      • website_urlstring (uri) or null

        Endorser’s website.

      • logo_urlstring (uri) or null

        Logo image.

      • statestring or null

        Home state; null for national endorsers.

      • partyParty or null

        The party, for a party or party club.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

    • contestContestBase
      19 fields
      • idinteger

        Contest id.

      • titlestring

        Title as printed on the ballot.

      • short_titlestring or null

        Shorter title.

      • kindstring

        candidate (an office), measure (a proposition) or retention (keep a judge?).

        One of candidate, measure, retention.
      • vote_methodstring

        How 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 of plurality, multi_seat, ranked_choice, approval, yes_no.
      • votes_allowedinteger or null

        How many choices a voter may mark (vote for N).

      • ranks_allowedinteger or null

        Ranked choice: how many choices a voter may rank.

      • measure_codestring or null

        The measure’s letter or number: “K”, “Prop 1”.

      • descriptionstring or null

        A short description or question. For a measure with ballot_text, often a summary.

      • ballot_textstring or null

        A measure’s wording exactly as printed on the ballot; null where we don’t have it yet.

      • full_text_urlstring (uri) or null

        The official full text of a measure.

      • unopposedboolean

        A candidate contest with no more candidates still running than seats (the same rule endorsedby.org uses). Always false for measures.

      • ballot_orderinteger or null

        Position on the printed ballot.

      • partyParty or null

        For a party primary, the party.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

      • detailsobject

        Facts about the contest. Only keys that apply are present.

        18 fields
        • uncontestedboolean

          Only one candidate is running.

        • nonpartisanboolean

          Candidates appear without a party.

        • measure_typestring

          bond, charter_amendment, initiative, referendum, tax, …

        • vote_requiredstring

          Share of the vote needed to pass, e.g. "2/3" or "55%".

        • majority_requiredboolean

          A candidate needs a majority to win outright (else a runoff).

        • amountnumber

          Dollar amount of a bond or tax measure.

        • termstring

          Length of the term, e.g. "4 years".

        • short_termboolean

          The seat is for the remainder of an unexpired term.

        • justicestring

          The judge or justice a retention question is about.

        • runoff_datestring (date)

          Date of a runoff, if one is needed.

        • stagestring

          Stage of a multi-round contest.

        • elected_at_largeboolean

          Elected by the whole jurisdiction, not by district.

        • none_of_theseboolean

          The ballot offers “None of these candidates” (Nevada).

        • primary_nominatingboolean

          The primary nominates a party’s candidate.

        • winnersinteger

          Number of winners, where it differs from votes_allowed.

        • ref_typestring

          Kind of referendum.

        • party_lines_unverifiedboolean

          The candidates are known, but their party lines haven’t been checked against a printed ballot yet.

        • question_wordingstring

          notice where description summarizes an official notice (Ohio) instead of quoting the ballot.

      • source_urlstring (uri) or null

        The official source the contest was loaded from.

      • districtDistrict

        The district a contest is voted in.

        6 fields
        • idinteger

          District id. Matches an officeholder’s district_id.

        • ocd_idstring or null

          Open Civic Data division id, used in districts= filters.

        • namestring

          District name.

        • levelstring

          Level of government.

          One of federal, state, county, city, school, special.
        • typestring

          Kind of district: statewide, congressional, state_upper, state_lower, county, council, school, …

        • statestring or null

          USPS state code.

      • resultContestResult or null

        The election result, once a source has one; null before then.

        17 fields
        • statusstring

          unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

          One of unofficial, certified.
        • source_kindstring

          Where 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 of official, compiled, party.
        • party_reportedboolean

          true when the result is a party’s announcement (source_kind party), not an official count.

        • total_votesinteger or null

          Votes cast in the contest: the printed choices plus write-ins. null on a party-reported result.

        • precincts_reportinginteger or null

          Precincts counted so far, where the source says.

        • precincts_totalinteger or null

          Precincts in the contest, where the source says.

        • sourceobject

          The source of the result.

          4 fields
          • namestring

            Who 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 null

            The contest’s name in the source, which may differ from the printed title.

          • updated_atstring (date-time) or null

            When the source last updated it, if it says.

        • official_urlstring (uri) or null

          For a compiled result, the state’s own certified results, where we have them.

        • more_sourcesarray of object

          Further announcements of the same result (party-reported results that more than one party published).

          2 fields
          • namestring or null

            Who published it.

          • urlstring (uri)

            Where.

        • write_in_votesinteger or null

          Votes for write-in candidates, where the source counts them.

        • blank_votesinteger or null

          Blank votes, where the source counts them.

        • over_votesinteger or null

          Overvotes, where the source counts them.

        • under_votesinteger or null

          Undervotes, where the source counts them.

        • other_choicesarray of OtherResultLine

          Lines the source reports that aren’t on the printed ballot, such as named write-ins.

          3 fields
          • labelstring or null

            As the source reports it: a write-in’s name, “Write-in”, or another line not on the printed ballot.

          • votesinteger or null

            Votes for it.

          • write_inboolean

            A write-in candidate.

        • rcv_roundsarray of object or null

          Ranked 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 null

          Anything 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.

      • electionElection
        10 fields
        • idinteger

          Election id.

        • slugstring

          Election slug, used in elections= filters: <state>-<date>-<type>.

        • namestring

          Election name.

        • short_namestring or null

          Shorter name.

        • datestring (date)

          Election day.

        • statestring or null

          State; null for a multi-state election.

        • typestring

          Kind of election.

          One of general, primary, special, special_primary, runoff, consolidated.
        • primary_systemstring or null

          How the state runs primaries.

          One of none, closed, semi_closed, open, top_two, nonpartisan.
        • info_urlstring (uri) or null

          Official election page.

        • detailsobject

          Voting rules for the election. Only keys that apply are present.

          12 fields
          • all_mailboolean

            Every voter is mailed a ballot.

          • ranked_choiceboolean

            Some contests in this election use ranked choice voting.

          • fusion_votingboolean

            Candidates can appear on more than one party’s line (New York, Connecticut).

          • partisan_primaryboolean

            A party primary.

          • primarystring

            Which primary, where a state holds several.

          • consolidated_withstring

            Elections 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_primaryboolean

            Louisiana’s open (jungle) primary.

          • notestring

            Anything else voters should know.

          • historicalboolean

            A past election kept for its results and history (the November 5, 2024 general, for example). Never shown to voters on endorsedby.org.

    • choiceChoice

      A line a voter can mark: a candidate, or a Yes/No answer.

      12 fields
      • idinteger

        Choice id: the choice_id endorsements refer to.

      • labelstring

        As printed: the candidate’s ballot name, or Yes / No.

      • measure_answerstring or null

        yes or no for a measure or retention question; null for a candidate.

        One of yes, no.
      • optionstring or null

        A special line: none_of_these (Nevada).

        One of none_of_these.
      • ballot_designationstring or null

        Occupation line printed under the name, e.g. “Transit Planner”.

      • withdrawnboolean

        The candidate withdrew but is still printed on the ballot.

      • incumbentboolean or null

        The 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 null

        For a measure answer: what a Yes (or No) vote means, as the election office words it.

      • candidateCandidate or null

        The candidate; null for a measure answer.

        4 fields
        • idinteger

          Candidate id.

        • full_namestring

          Full name.

        • website_urlstring (uri) or null

          Campaign website.

        • photo_urlstring (uri) or null

          Photo.

      • partyParty or null

        The party (or party preference) printed beside the name.

        3 fields
        • slugstring

          Party slug.

        • namestring

          Party name.

        • abbreviationstring or null

          Short form as printed, e.g. DEM.

      • party_labelstring or null

        The party text exactly as printed, where it differs (fusion lines, top-two preferences).

      • resultChoiceResult or null

        This choice’s votes, once the contest has a result; null before then.

        3 fields
        • votesinteger or null

          Votes for this choice. null only on a party-reported result, which names a winner without counts.

        • vote_pctnumber or null

          Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

        • winnerboolean or null

          true 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

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/endorsements/{id}
curl "https://api.ballotdata.ai/v1/endorsements/90211" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/endorsements/90211" \
  --data-urlencode "lang=es" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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

  • idintegerrequired

    Endorsement id (from GET /v1/endorsements, or one you stored earlier).

    At least 1.Example 90211

Returns

200 Every change, oldest first.

  • dataarray of HistoryEntry

    Every 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.

    • changestring

      What happened.

      One of created, updated, deleted.
    • changedarray of string

      For updated: the fields that changed. Empty otherwise.

      One of choice_id, rank, statement, source_url, published_on.
    • beforeHistoryValues or null

      The values before the change; null for created.

      6 fields
      • choice_idinteger

        The choice endorsed.

      • choice_labelstring or null

        That choice’s label as printed; null if the choice has since been removed from the ballot.

      • rankinteger or null

        Ranked choice: the rank; null otherwise.

      • statementstring or null

        What the endorser said.

      • source_urlstring (uri) or null

        Where it was published.

      • published_onstring (date) or null

        When it was published.

    • afterHistoryValues or null

      The values after the change; null for deleted.

      6 fields
      • choice_idinteger

        The choice endorsed.

      • choice_labelstring or null

        That choice’s label as printed; null if the choice has since been removed from the ballot.

      • rankinteger or null

        Ranked choice: the rank; null otherwise.

      • statementstring or null

        What the endorser said.

      • source_urlstring (uri) or null

        Where it was published.

      • published_onstring (date) or null

        When it was published.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/endorsements/{id}/history
curl "https://api.ballotdata.ai/v1/endorsements/90211/history" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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.

    Example 2026-11-01T00:00:00Z
  • categoriescomma-separated strings

    Endorser category slugs (GET /v1/endorser-categories).

    Example labor
  • endorserscomma-separated strings

    Endorser slugs (GET /v1/endorsers).

    Example sf-labor-council
  • electionscomma-separated strings

    Election slugs (GET /v1/elections).

    Example ca-2026-11-03-general
  • statescomma-separated strings

    States the contests are voted in (USPS codes).

    Example CA
  • limitinteger

    Results per page, 1–1000.

    Default 100.1 to 1000.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of changes, oldest first.

  • dataarray of Change

    This page.

    10 fields
    • idinteger

      The change’s id: increasing and unique. Pages are in this order.

    • atstring (date-time)

      When the change was made.

    • changestring

      What happened.

      One of created, updated, deleted.
    • changedarray of string

      For updated: the fields that changed. Empty otherwise.

      One of choice_id, rank, statement, source_url, published_on.
    • endorsement_idinteger

      The endorsement changed.

    • endorserobject
      4 fields
      • idinteger

        Endorser id.

      • slugstring or null

        Endorser slug; null if the endorser has since been removed.

      • namestring or null

        Endorser name; null if the endorser has since been removed.

      • categoryCategory or null

        Category.

        2 fields
        • slugstring

          Category slug, used in categories= filters.

        • namestring

          Display name.

    • contestobject
      2 fields
      • idinteger

        Contest id.

      • titlestring

        Title as printed on the ballot.

    • electionobject
      3 fields
      • idinteger

        Election id.

      • slugstring

        Election slug.

      • datestring (date)

        Election day.

    • beforeHistoryValues or null

      The values before the change; null for created.

      6 fields
      • choice_idinteger

        The choice endorsed.

      • choice_labelstring or null

        That choice’s label as printed; null if the choice has since been removed from the ballot.

      • rankinteger or null

        Ranked choice: the rank; null otherwise.

      • statementstring or null

        What the endorser said.

      • source_urlstring (uri) or null

        Where it was published.

      • published_onstring (date) or null

        When it was published.

    • afterHistoryValues or null

      The values after the change; null for deleted.

      6 fields
      • choice_idinteger

        The choice endorsed.

      • choice_labelstring or null

        That choice’s label as printed; null if the choice has since been removed from the ballot.

      • rankinteger or null

        Ranked choice: the rank; null otherwise.

      • statementstring or null

        What the endorser said.

      • source_urlstring (uri) or null

        Where it was published.

      • published_onstring (date) or null

        When it was published.

  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/changes
curl "https://api.ballotdata.ai/v1/changes?since=2026-11-01T00%3A00%3A00Z&states=CA" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • qstring

    Name contains (case-insensitive, up to 100 characters).

    Example doe
  • statescomma-separated strings

    States the contests are voted in (USPS codes).

    Example CA
  • electionscomma-separated strings

    Election slugs (GET /v1/elections).

    Example ca-2026-11-03-general
  • incumbentboolean

    true for only incumbents, false for only candidates known not to hold the seat. Leave it out for everyone.

    Example true
  • upcomingboolean

    true for only elections from yesterday on; false (the default) for every election.

    Default false.Example true
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–500.

    Default 100.1 to 500.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of candidates, by id.

  • dataarray of CandidateSummary

    This page.

    6 fields
    • idinteger

      Candidate id.

    • full_namestring

      Full name.

    • website_urlstring (uri) or null

      Campaign website.

    • photo_urlstring (uri) or null

      Photo.

    • incumbentboolean or null

      Holds the seat now: true or false where we know, null where we don’t yet.

    • contestsarray of CandidateContest

      The contests they’re on that you can read, newest election first.

      6 fields
      • contestobject
        6 fields
        • idinteger

          Contest id.

        • titlestring

          Title as printed on the ballot.

        • kindstring

          candidate or retention.

          One of candidate, measure, retention.
        • vote_methodstring

          How votes are cast.

          One of plurality, multi_seat, ranked_choice, approval, yes_no.
        • votes_allowedinteger or null

          Vote for N.

        • ranks_allowedinteger or null

          Ranked choice: how many choices a voter may rank.

      • districtobject
        5 fields
        • idinteger

          District id.

        • ocd_idstring or null

          OCD division id.

        • namestring

          District name.

        • levelstring

          Level.

          One of federal, state, county, city, school, special.
        • statestring or null

          USPS state code.

      • electionobject
        4 fields
        • idinteger

          Election id.

        • slugstring

          Election slug.

        • namestring

          Election name.

        • datestring (date)

          Election day.

      • choiceobject
        7 fields
        • idinteger

          Choice id.

        • labelstring

          The name as printed.

        • ballot_designationstring or null

          Occupation line printed under the name.

        • withdrawnboolean

          Withdrew but still printed.

        • partyParty or null

          The party printed beside the name.

          3 fields
          • slugstring

            Party slug.

          • namestring

            Party name.

          • abbreviationstring or null

            Short form as printed, e.g. DEM.

        • party_labelstring or null

          The party text exactly as printed, where it differs.

        • resultChoiceResult or null

          The candidate’s votes and whether they won, once the contest has a result.

          3 fields
          • votesinteger or null

            Votes for this choice. null only on a party-reported result, which names a winner without counts.

          • vote_pctnumber or null

            Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

          • winnerboolean or null

            true 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 null

        The contest’s result, once a source has one.

        5 fields
        • statusstring

          unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

          One of unofficial, certified.
        • source_kindstring

          Where 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 of official, compiled, party.
        • party_reportedboolean

          true when the result is a party’s announcement (source_kind party), not an official count.

        • total_votesinteger or null

          Votes cast in the contest. null on a party-reported result.

        • source_urlstring (uri)

          Where the result was published.

      • endorsement_countinteger

        Endorsers who endorsed this candidate in this contest.

  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/candidates
curl "https://api.ballotdata.ai/v1/candidates?q=doe&states=CA" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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

  • idintegerrequired

    Candidate id (from GET /v1/candidates, or a choice’s candidate.id).

    At least 1.Example 8811

Query parameters

  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 The candidate.

  • dataCandidateDetail
    7 fields
    • idinteger

      Candidate id.

    • full_namestring

      Full name.

    • website_urlstring (uri) or null

      Campaign website.

    • photo_urlstring (uri) or null

      Photo.

    • incumbentboolean or null

      Holds the seat now: true or false where we know, null where we don’t yet.

    • contestsarray of object

      The contests they’re on that you can read, newest election first, each with the endorsements received.

      7 fields
      • contestobject
        6 fields
        • idinteger

          Contest id.

        • titlestring

          Title as printed on the ballot.

        • kindstring

          candidate or retention.

          One of candidate, measure, retention.
        • vote_methodstring

          How votes are cast.

          One of plurality, multi_seat, ranked_choice, approval, yes_no.
        • votes_allowedinteger or null

          Vote for N.

        • ranks_allowedinteger or null

          Ranked choice: how many choices a voter may rank.

      • districtobject
        5 fields
        • idinteger

          District id.

        • ocd_idstring or null

          OCD division id.

        • namestring

          District name.

        • levelstring

          Level.

          One of federal, state, county, city, school, special.
        • statestring or null

          USPS state code.

      • electionobject
        4 fields
        • idinteger

          Election id.

        • slugstring

          Election slug.

        • namestring

          Election name.

        • datestring (date)

          Election day.

      • choiceobject
        7 fields
        • idinteger

          Choice id.

        • labelstring

          The name as printed.

        • ballot_designationstring or null

          Occupation line printed under the name.

        • withdrawnboolean

          Withdrew but still printed.

        • partyParty or null

          The party printed beside the name.

          3 fields
          • slugstring

            Party slug.

          • namestring

            Party name.

          • abbreviationstring or null

            Short form as printed, e.g. DEM.

        • party_labelstring or null

          The party text exactly as printed, where it differs.

        • resultChoiceResult or null

          The candidate’s votes and whether they won, once the contest has a result.

          3 fields
          • votesinteger or null

            Votes for this choice. null only on a party-reported result, which names a winner without counts.

          • vote_pctnumber or null

            Share of the contest’s votes, 0 to 100, as the source gives it (three decimals).

          • winnerboolean or null

            true 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 null

        The contest’s result, once a source has one.

        5 fields
        • statusstring

          unofficial (election-night and canvass counts, still changing) or certified (the canvass is certified).

          One of unofficial, certified.
        • source_kindstring

          Where 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 of official, compiled, party.
        • party_reportedboolean

          true when the result is a party’s announcement (source_kind party), not an official count.

        • total_votesinteger or null

          Votes cast in the contest. null on a party-reported result.

        • source_urlstring (uri)

          Where the result was published.

      • endorsement_countinteger

        Endorsers who endorsed this candidate in this contest.

      • endorsementsarray of CandidateEndorsement

        Every endorsement they received in this contest, by endorser name.

        7 fields
        • endorsement_idinteger

          Endorsement id.

        • endorserEndorserRef

          Who made an endorsement.

          5 fields
          • idinteger

            Endorser id.

          • slugstring

            Endorser slug, used in endorsers= filters.

          • namestring

            Endorser name.

          • categoryCategory

            The kind of endorser.

          • statestring or null

            Home state; null for national endorsers.

        • rankinteger or null

          Ranked choice: the rank the endorser gave; null otherwise.

        • statementstring or null

          What the endorser said.

        • source_urlstring (uri) or null

          Where it was published.

        • published_onstring (date) or null

          When it was published.

        • added_bystring

          How 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 of automatic, staff, data_load.
    • issue_positionsarray of IssuePosition

      Where they stand on common issues (GET /v1/issues), in issue order, each with its sources. Empty where we have none yet.

      7 fields
      • idinteger

        Position id.

      • issueobject
        2 fields
        • slugstring

          Issue slug.

        • namestring

          Issue name.

      • stancestring or null

        The 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 of supports, opposes, mixed.
      • summarystring

        Where they stand, in neutral words: what they said, or how they voted.

      • quotestring or null

        Their own words, verbatim, where a source has them.

      • checked_onstring (date)

        When the sources were last read.

      • sourcesarray of IssuePositionSource

        Every link the position was taken from; at least one.

        5 fields
        • urlstring (uri)

          Where the position comes from.

        • kindstring

          What 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 of candidate_website, voting_record, official_statement, questionnaire, debate, news.
        • publisherstring

          Who published it: “U.S. Senate”, “The Atlanta Journal-Constitution”.

        • titlestring or null

          Its title.

        • published_onstring (date) or null

          When it was published, where known.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
GET /v1/candidates/{id}
curl "https://api.ballotdata.ai/v1/candidates/8811" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/candidates/8811" \
  --data-urlencode "lang=es" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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 Issue

    Every item.

    4 fields
    • slugstring

      Issue slug, for issues= filters.

    • namestring

      Short name: “Abortion”.

    • questionstring

      The yes/no question a position’s stance answers.

    • descriptionstring

      What the issue covers.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/issues
curl "https://api.ballotdata.ai/v1/issues" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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 strings

    Issue slugs (GET /v1/issues).

    Example abortion,guns
  • stancescomma-separated strings

    Stances: supports, opposes or mixed.

    One of supports, opposes, mixed.Example supports
  • candidatescomma-separated integers

    Candidate ids.

    Example 8811
  • contestscomma-separated integers

    Contest ids: positions of the candidates on these contests.

    Example 4410
  • electionscomma-separated strings

    Election slugs (GET /v1/elections).

    Example ca-2026-11-03-general
  • statescomma-separated strings

    States the contests are voted in (USPS codes).

    Example CA
  • limitinteger

    Results per page, 1–1000.

    Default 100.1 to 1000.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of positions, by id.

  • dataarray of IssuePositionItem

    This page.

    9 fields
    • idinteger

      Position id.

    • issueobject
      2 fields
      • slugstring

        Issue slug.

      • namestring

        Issue name.

    • stancestring or null

      The 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 of supports, opposes, mixed.
    • summarystring

      Where they stand, in neutral words: what they said, or how they voted.

    • quotestring or null

      Their own words, verbatim, where a source has them.

    • checked_onstring (date)

      When the sources were last read.

    • sourcesarray of IssuePositionSource

      Every link the position was taken from; at least one.

      5 fields
      • urlstring (uri)

        Where the position comes from.

      • kindstring

        What 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 of candidate_website, voting_record, official_statement, questionnaire, debate, news.
      • publisherstring

        Who published it: “U.S. Senate”, “The Atlanta Journal-Constitution”.

      • titlestring or null

        Its title.

      • published_onstring (date) or null

        When it was published, where known.

    • candidateobject
      2 fields
      • idinteger

        Candidate id.

      • full_namestring

        Full name.

    • contestsarray of object

      The candidate’s contests that you can read (and that match the filters), newest election first.

      4 fields
      • idinteger

        Contest id.

      • titlestring

        Title as printed on the ballot.

      • electionobject
        2 fields
        • slugstring

          Election slug.

        • datestring (date)

          Election day.

      • statestring

        USPS state code.

  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
GET /v1/issue-positions
curl "https://api.ballotdata.ai/v1/issue-positions?issues=abortion&states=CA" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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 strings

    USPS codes.

    Example CA
  • officescomma-separated strings

    Offices held.

    One of us_senator, us_representative, governor, lieutenant_governor, attorney_general, secretary_of_state, chief_election_officer, treasurer, auditor, state_senator, state_representative, state_legislator, city_council.Example us_senator,governor
  • levelscomma-separated strings

    Levels of office: federal, state, or city (the D.C. Council).

    One of federal, state, city.Example federal
  • districtscomma-separated strings

    OCD division ids of the seats.

    Example ocd-division/country:us/state:ca/sldl:17
  • qstring

    Name contains (case-insensitive, up to 100 characters).

    Example morgan
  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es
  • limitinteger

    Results per page, 1–500.

    Default 100.1 to 500.Example 20
  • cursorstring

    Where 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.

    Example aTo0NDEw

Returns

200 A page of officeholders.

  • dataarray of Officeholder

    This page.

    23 fields
    • person_idstring (uuid)

      Open States person id; stays the same across terms and offices.

    • namestring

      Full name.

    • given_namestring or null

      Given name.

    • family_namestring or null

      Family name.

    • officestring

      The office.

      One of us_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_titlestring

      Title the state uses: “Assemblymember”, “Delegate”.

    • levelstring

      Level of government.

      One of federal, state, city.
    • statestring

      USPS state code.

    • districtstring or null

      District as the legislature names it: "12", "1A", "21st Middlesex".

    • district_ocd_idstring

      OCD division id of the seat.

    • district_idinteger or null

      The matching contest district id, where there is one.

    • partystring or null

      Party as published: “Democratic”, “Republican/Conservative”.

    • party_abbreviationstring or null

      Party abbreviation.

    • term_startstring (date) or null

      Start of the current term.

    • term_endstring (date) or null

      End of the current term.

    • emailstring (email) or null

      Office email.

    • phonestring or null

      Office phone (capitol office first).

    • website_urlstring (uri) or null

      Official page.

    • image_urlstring (uri) or null

      Official photo.

    • officesarray of object

      Offices with their addresses and phones.

      4 fields
      • classificationstring

        capitol, district, …

      • addressstring

        Mailing address.

      • voicestring

        Phone.

      • faxstring

        Fax.

    • socialobject

      Social accounts, where known.

      4 fields
      • twitterstring

        Handle.

      • facebookstring

        Handle.

      • instagramstring

        Handle.

      • youtubestring

        Handle.

    • source_urlsarray of string (uri)

      Where the data came from.

    • updated_atstring (date-time)

      Last refreshed.

  • next_cursorstring or null

    Pass as cursor= for the next page; null on the last page.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 429 Over 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.
  • 503 Not ready yet on this server. Wait Retry-After seconds.
GET /v1/officeholders
curl "https://api.ballotdata.ai/v1/officeholders?states=CA&offices=state_representative" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
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"
Response200application/json
{
  "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)required

    The officeholder’s person_id (from GET /v1/officeholders).

    Example 4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b

Query parameters

  • langstring

    es for Spanish ballot text (titles, choices, measure text, statements) where the state publishes it; English otherwise. Names are never translated.

    One of en, es.Default en.Example es

Returns

200 The officeholder.

  • dataOfficeholderDetail
    24 fields
    • person_idstring (uuid)

      Open States person id; stays the same across terms and offices.

    • namestring

      Full name.

    • given_namestring or null

      Given name.

    • family_namestring or null

      Family name.

    • officestring

      The office.

      One of us_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_titlestring

      Title the state uses: “Assemblymember”, “Delegate”.

    • levelstring

      Level of government.

      One of federal, state, city.
    • statestring

      USPS state code.

    • districtstring or null

      District as the legislature names it: "12", "1A", "21st Middlesex".

    • district_ocd_idstring

      OCD division id of the seat.

    • district_idinteger or null

      The matching contest district id, where there is one.

    • partystring or null

      Party as published: “Democratic”, “Republican/Conservative”.

    • party_abbreviationstring or null

      Party abbreviation.

    • term_startstring (date) or null

      Start of the current term.

    • term_endstring (date) or null

      End of the current term.

    • emailstring (email) or null

      Office email.

    • phonestring or null

      Office phone (capitol office first).

    • website_urlstring (uri) or null

      Official page.

    • image_urlstring (uri) or null

      Official photo.

    • officesarray of object

      Offices with their addresses and phones.

      4 fields
      • classificationstring

        capitol, district, …

      • addressstring

        Mailing address.

      • voicestring

        Phone.

      • faxstring

        Fax.

    • socialobject

      Social accounts, where known.

      4 fields
      • twitterstring

        Handle.

      • facebookstring

        Handle.

      • instagramstring

        Handle.

      • youtubestring

        Handle.

    • source_urlsarray of string (uri)

      Where the data came from.

    • updated_atstring (date-time)

      Last refreshed.

    • seatsarray of Officeholder

      Every seat they hold (rarely more than one).

      23 fields
      • person_idstring (uuid)

        Open States person id; stays the same across terms and offices.

      • namestring

        Full name.

      • given_namestring or null

        Given name.

      • family_namestring or null

        Family name.

      • officestring

        The office.

        One of us_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_titlestring

        Title the state uses: “Assemblymember”, “Delegate”.

      • levelstring

        Level of government.

        One of federal, state, city.
      • statestring

        USPS state code.

      • districtstring or null

        District as the legislature names it: "12", "1A", "21st Middlesex".

      • district_ocd_idstring

        OCD division id of the seat.

      • district_idinteger or null

        The matching contest district id, where there is one.

      • partystring or null

        Party as published: “Democratic”, “Republican/Conservative”.

      • party_abbreviationstring or null

        Party abbreviation.

      • term_startstring (date) or null

        Start of the current term.

      • term_endstring (date) or null

        End of the current term.

      • emailstring (email) or null

        Office email.

      • phonestring or null

        Office phone (capitol office first).

      • website_urlstring (uri) or null

        Official page.

      • image_urlstring (uri) or null

        Official photo.

      • officesarray of object

        Offices with their addresses and phones.

        4 fields
        • classificationstring

          capitol, district, …

        • addressstring

          Mailing address.

        • voicestring

          Phone.

        • faxstring

          Fax.

      • socialobject

        Social accounts, where known.

        4 fields
        • twitterstring

          Handle.

        • facebookstring

          Handle.

        • instagramstring

          Handle.

        • youtubestring

          Handle.

      • source_urlsarray of string (uri)

        Where the data came from.

      • updated_atstring (date-time)

        Last refreshed.

Errors

  • 400 A parameter is missing or not valid.
  • 401 No API key, or one that isn’t valid, was revoked or expired.
  • 403 The 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.
  • 404 Nothing has that id or slug.
  • 429 Over 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.
  • 503 Not ready yet on this server. Wait Retry-After seconds.
GET /v1/officeholders/{id}
curl "https://api.ballotdata.ai/v1/officeholders/4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
With every parameter
curl -G "https://api.ballotdata.ai/v1/officeholders/4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b" \
  --data-urlencode "lang=es" \
  -H "Authorization: Bearer $BALLOTDATA_API_KEY"
Response200application/json
{
  "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"
      }
    ]
  }
}
API reference · BallotData API