Open States API Guide: v3 Endpoints, Keys, Bulk Data, and Licensing

Title card for the Vaquill AI guide: Open States API Guide: v3 Endpoints, Keys, Bulk Data, and Licensing

Short answer: the Open States v3 API is a free REST API for state legislative data: bills, legislators, committees, events and jurisdictions for the 50 states, DC and Puerto Rico. You register for a key and send it in an X-API-KEY header. Bills and legislators are the mature parts. Committees and events are marked experimental, with uneven state coverage. The same data is available as bulk downloads under a public domain dedication. Open States tracks what legislators do. For what the code says today, pair it with a statute source.

TL;DR

  • Open States v3 has five resource groups: jurisdictions, people, bills, committees and events, all behind https://v3.openstates.org.
  • Auth is a key you get from your account, sent as X-API-KEY or ?apikey. A request without one gets a 403 that says so.
  • Its edge is people data. You can look up legislators by name, district or map coordinates, and see their offices and current role.
  • Committees and events are experimental. Check a state's data before you build a feature on it.
  • Bulk files (CSV, JSON, PostgreSQL dumps) are public domain, and the code that collects the data is open source on GitHub.
What would you do?
Question 1 of 4

A user types a home address into your civic app and expects to see their state legislators. Which approach holds up best?

Part of our MCP and developer guide series.

For the commercial side of state bill tracking, read our LegiScan API guide. For the law that bills turn into, see the US statutes API guide and the best legal data APIs for developers.

What Open States is

Searches for the openstates api usually mean the v3 REST API at v3.openstates.org. Open States is an open-source project that collects legislative information from every state, standardizes it and publishes it. Its documentation describes coverage of all 50 states, Washington DC and Puerto Rico, delivered through an API and bulk downloads. The project now publishes through Plural Policy. Municipal governments appear as jurisdictions too, with limited support, and the changelog mentions experimental US federal support.

Everything here is read from Open States' own documentation, its API v3 changelog, its terms and the OpenAPI document served at v3.openstates.org, checked in October 2026. The OpenAPI document carries the version string 2021.11.12, so confirm any field you depend on against the interactive docs.

A short history of Open States

The project started in 2009 inside the Sunlight Foundation. Its founder, James Turk, wrote that the idea was "a new data source focused on state legislatures, built and led by a team of volunteer developers around the country," and that the work really kicked off at PyCon 2009. It was first called the Fifty State Project and later renamed Open States. By 2016 Turk reported that it was gathering data every day on over 7,000 legislators and tens of thousands of bills a year. He also said the API had become Sunlight's most popular data resource, at tens of millions of requests a year, and that more than 75 people had contributed code. The full website launched in 2013. (All of that is from Turk's own 2016 account on Opensource.com.)

Then the funding ran out. Sunlight Labs closed in 2016, and the project went independent. Turk later described the setup this way: "the team of ex-Sunlight employees that inherited the project all had day jobs, and we were depending on small grants and donations to keep the project afloat." Grants in 2020 let him work on it full time, but they were finite. In 2021 Plural, a legislative software company, adopted the project, and Turk joined it as Director of Public Data. The Open States about page lists the same milestones, and says that as of 2023 the core team is led by Plural staff, with Turk staying on as Founder Emeritus.

One detail from Turk's announcement of the move matters for anyone choosing a licence-friendly source. He said the team chose not to ban commercial use, because commercial users could give back too. The cost was real. At least one startup took the code wholesale and adapted it privately, which he called "perfectly legal" but not in the spirit of the project. That choice is why the data and code are as open as they are today.

Open States API v3 endpoints

ResourcePathWhat you get
Jurisdictions/jurisdictions, /jurisdictions/{id}States and other governments, chambers, legislative sessions, latest scrape runs
People/people, /people.geoLegislators, party, current role, district, offices, links
Bills/bills, /bills/{jurisdiction}/{session}/{id}, /bills/ocd-bill/{uuid}Search, status, sponsors, actions, versions, documents, votes
Committees/committees, /committees/{id}Committees and subcommittees, with memberships
Events/events, /events/{id}Hearings and meetings, participants, agenda items, documents

Bill text and versions in these responses are the text of proposals. The codified statute is a separate object that this API does not serve, which is why the last section joins the two.

Lists are paginated. Each response has a results array and a pagination object with per_page, page, max_page and total_items. The defaults in the OpenAPI document are 10 per page for bills and people, and 20 for committees and events.

Most detail comes through an include parameter that you repeat. For a bill you can ask for sponsorships, actions, versions, documents, votes, abstracts, sources, other_titles, other_identifiers and related_bills. Leave them off and the response stays small.

Making a first call

Register at your Open States account page to get a key. The header is the better habit, because a key in a URL ends up in logs and browser history. Then:

curl -H "X-API-KEY: $OPENSTATES_API_KEY" \
  "https://v3.openstates.org/bills?jurisdiction=Texas&q=data+privacy&sort=updated_desc&include=sponsorships&per_page=5"

The /bills search needs either a jurisdiction or a full-text query q. Other filters include session, chamber, identifier, classification, subject, sponsor, updated_since, created_since and action_since. Sort options are updated, first_action and latest_action, each ascending or descending.

Leave the key off and you get a plain answer. We called the live endpoint without one and received a 403 with the message Must provide API Key as ?apikey or X-API-KEY. If you see that, the request reached the right server and the key is the problem.

A Bill carries an id such as ocd-bill/<uuid>, a session, an identifier such as SB 113, a title, classification, subject list, first_action_date, latest_action_date, latest_action_description, latest_passage_date and an openstates_url that links to the project's own page for the bill. Session identifiers are state-specific strings. The docs show examples like 2020 and 2022S1, and bill identifiers in the examples carry a space (SB 113), so match that form when you filter on identifier. The same identifier recurs in every session, so always carry the session with the bill.

The OpenAPI document's example for one bill result looks like this (values are its placeholders, not live data):

{
  "id": "ocd-bill/f0049138-1ad8-4506-a2a4-f4dd1251bbba",
  "session": "2020",
  "identifier": "SB 113",
  "title": "Adopting a State Scorpion",
  "classification": ["resolution"],
  "subject": ["SCORPIONS", "SYMBOLS"],
  "openstates_url": "https://openstates.org/nc/bills/2019/HB1105/"
}

People are the strong suit

Legislator data is where Open States does the most for you. The /people endpoint needs a jurisdiction, a name or one or more IDs, and it accepts district and org_classification (upper or lower) filters. Name matching is fuzzy, which the changelog notes since December 2020. Add include=offices to get capitol and district offices with addresses and phone numbers, or other_identifiers and links for cross-references.

Each person has a current_role with a title, a chamber classification, a district and an ocd-division ID, plus party, email, image and an openstates_url.

/people.geo is the endpoint that saves the most work. Give it a latitude and longitude and it returns the legislators currently representing that point, which replaces a district-lookup project. Per the OpenAPI description it is limited to state legislators and US Congress. Governors and mayors are not included.

curl -H "X-API-KEY: $OPENSTATES_API_KEY" \
  "https://v3.openstates.org/people.geo?lat=30.2747&lng=-97.7404"

If you build constituent tools, the interesting design question is the IDs. Bill sponsorships carry a person.id of the form ocd-person/<uuid>, and the same ID comes back from /people and /people.geo. That lets you answer questions like "did any of this user's own legislators sponsor this bill?" with a set intersection.

Committees and events, with a caveat

Committees list by jurisdiction, classification (committee or subcommittee), parent and chamber. Add include=memberships to get each member's person_name, role and linked person. Events list by jurisdiction and take before, after, deleted and require_bills filters. Event detail includes the location, participants, agenda items, documents and media.

Other legislative APIs tend to treat committee work as a field on the bill. Open States models committees and hearings as their own resources, which fits a calendar or a "who is voting on my issue next week" feature.

The OpenAPI description marks both endpoints experimental, with coverage that varies by state and a response format that may change. The changelog lists them as experimental additions from August and September 2021. Spot-check the state you care about before you build on them, and confirm the data is still being updated. The latest_bill_update and latest_people_update fields on each jurisdiction help with that.

What developers say

You can learn a lot about a data source from the people who keep it running. In March 2026 a Hacker News commenter who said he had worked on legislative data for 15 years, including on the Open States scrapers, described the problem bluntly: "this space is a classic nerd tar pit." His reason was that the data is "very complicated, and the sources constantly change and break in goofy ways." Missing a bill costs too much for a scraper to just skip a broken page (showerst, Hacker News, March 2026).

That is a fair warning about the committee and hearing endpoints above. Fifty-plus state sites, each with its own quirks, will never line up evenly. It also explains why this guide keeps telling you to check a state before you build on it.

The praise comes from people who tried the do-it-yourself route first. When Sunlight Labs shut down in 2016, one developer wrote: "I used their openstates repo a while back and found it amazing how well they could distill the data. I had tried writing scrapers before for the same purposes, but it took forever just to get a fraction of what they had." (salbertson, Hacker News, October 2016).

Keys and rate limits

API keys are required. The changelog records that rate limiting was added in October 2020. The public documentation does not publish limit numbers or a tier table, so we do not quote any. Plan for throttling: cache jurisdiction and session lists, page with per_page, use updated_since for incremental syncs and request only the include sections you read. For higher volume, the project lists contact@openstates.org for questions.

Bulk data and licensing

If you need a whole session at once, skip the API. The Open States data page offers:

FormatContentUpdate cadence stated
CSV and JSONBills and votes per session, JSON includes full textMonthly
PostgreSQL dumpsThe public databaseThroughout the month, typically a day or two behind
YAMLLegislator data, in the openstates/people repositoryAs needed during session
Geographic JSONDistrict polygonsLast updated November 2018

The API also exposes per-session download links in the legislative_sessions include of the jurisdiction, added in November 2021.

On licensing, the data page states that unless otherwise noted, data is provided under a public domain dedication, and that attribution is greatly appreciated. The terms of use go further: Open States makes no copyright claim over the data it collects and publishes, and no attribution is required, though no affiliation or endorsement may be implied for a derivative product. The collection code is open source too. The scrapers repository is licensed GPL-3.0. Read the terms yourself before you ship, because they also cover the service.

From a section back to its sponsors

A legislature produces bills, and a bill becomes law only if it passes and is signed. Open States tells you the status of the bill, its sponsors, its committee path and the hearings. The code tells you what survived. The useful direction for a legislator-centered product runs backward: start with a section of law and find the people behind it.

Most state codes print a history line under each section that names the session and bill that created or changed it. Resolve a citation, read the line, then ask Open States for that bill.

Loading diagram...

The first half uses the Vaquill AI resolve endpoint, which turns a Bluebook-style citation into the exact section and returns its history field. The citation resolution guide covers its request and response in full. We ran this half against the live API, and it returns a history string like the one in the comment below. The Open States half follows the v3 parameters in the OpenAPI document and needs your own key.

import os
import re
import time
import requests

VQ = "https://api.vaquill.ai/api/v1/us/statutes/resolve"
OS = "https://v3.openstates.org"
vq_headers = {"Authorization": f"Bearer {os.environ['VAQUILL_API_KEY']}"}
os_headers = {"X-API-KEY": os.environ["OPENSTATES_API_KEY"]}


def get(url, **kw):
    """GET with one back-off retry when the server throttles or hiccups."""
    for attempt in range(2):
        r = requests.get(url, timeout=30, **kw)
        if r.status_code not in (429, 502, 503):
            r.raise_for_status()
            return r.json()
        time.sleep(float(r.headers.get("Retry-After", 5)))
    r.raise_for_status()

# 1. Resolve a citation and read the section's history line.
data = get(VQ, headers=vq_headers,
           params={"cite": "Tex. Bus. & Com. Code 541.051"})
if not data["resolved"]:
    raise SystemExit("citation did not resolve")
history = data["section"]["history"] or ""
# "Added by Acts 2023, 88th Leg., R.S., Ch. 995 (H.B. 4), Sec. 2, eff. July 1, 2024."

# 2. Pull the year and bill number out of it.
m = re.search(r"Acts (\d{4}),.*?\(([HS])\.B\. (\d+)\)", history)
if not m:
    raise SystemExit("history line is not in the Texas format")
year, house, number = m.group(1), m.group(2), int(m.group(3))

# 3. Ask Open States for that bill. Identifiers repeat across sessions, so keep
#    the one whose first action falls in the year of the Act or the year before
#    (Texas bills can be prefiled in November).
bills = get(f"{OS}/bills", headers=os_headers, params=[
    ("jurisdiction", "Texas"),
    ("identifier", f"{house}B {number}"),
    ("include", "sponsorships"),
    ("per_page", 5),
])
sponsors = []
for bill in bills["results"]:
    first_year = int((bill.get("first_action_date") or "0000")[:4])
    if first_year in (int(year) - 1, int(year)):
        print(bill["session"], bill["identifier"], bill["title"][:60])
        sponsors = [s for s in bill.get("sponsorships", []) if s.get("person")]

# 4. Which of those sponsors represent this location? (Texas Capitol)
geo = get(f"{OS}/people.geo", headers=os_headers,
          params={"lat": 30.2747, "lng": -97.7404})
mine = {p["id"] for p in geo["results"]}
for s in sponsors:
    if s["person"]["id"] in mine:
        print("Local sponsor:", s["name"], "primary" if s["primary"] else "cosponsor")

Dates matter here for a second reason. The Act in this example passed in 2023, but the section's history line gives an effective date of July 1, 2024. Open States tells you when the legislature acted. The code tells you when the rule started to apply, so show users both.

Step 3 filters by date because the same identifier, such as HB 4, exists in every session. For a cleaner filter, read the session identifiers from the jurisdiction's legislative_sessions include and pass session to /bills.

Two limits apply. History lines are formatted differently in each state, so the regex above is Texas-shaped. A section's history also names only the bills that changed that section, so a bill that touched many sections shows up once per section. For the other direction, from a bill to the sections it touched, see the LegiScan guide.

Open States or a paid tracker?

Pick Open States when you want legislators, districts, committees or hearing schedules, or when you want a free key and open licensing. Pick a paid tracker such as LegiScan when you need a push feed with stated delivery times, or federal and state bills in one record. Many teams use both: one for people and calendars, the other for bill volume and alerts.

For the code itself, use a statute source. The Vaquill AI statutes API returns a section by citation with its status and effective date for the 50 states, DC and Puerto Rico.

Sources

Open States facts come from the API v3 overview, the interactive v3 docs, the bulk data page, the terms of use and the scrapers repository, all read in October 2026.

This guide is part of US Law Data: The Complete Guide, a map of where US law comes from and how to use it.

FAQ

Is the Open States API free? Yes, you can register for a key at no cost. The documentation says keys are required and that rate limiting applies, but it does not publish limit numbers. Contact the project for higher volume.

How do I authenticate with the Open States API? Send your key in an X-API-KEY header, or as an apikey query parameter. The base URL is https://v3.openstates.org. A request with no key returns a 403 that names both options.

What is the difference between Open States and LegiScan? Both cover state bills. Open States is open source, models people, committees and events as their own resources, and publishes public domain bulk data. LegiScan is a commercial service with free and paid levels, covers Congress as well, and offers a push feed. Our LegiScan API guide covers its levels in detail.

How complete are the committee and event endpoints? Coverage varies by state. The OpenAPI description marks both as experimental and warns that the response format may change, so test the state you need before you build a production feature on them.

Can I download Open States data in bulk? Yes. The data page offers CSV and JSON per session, PostgreSQL dumps, YAML for legislators and geographic JSON for districts. Bill and vote files update monthly, and database dumps update throughout the month.

Can I use Open States data commercially? The data page describes a public domain dedication and the terms say no copyright is claimed and no attribution is required. You may not imply affiliation or endorsement. Read the terms of use for the service yourself before launch.

Does Open States have the text of statutes? It has bill text and versions, and bulk JSON includes full text. It does not publish the codified statute book. For current section text by citation, use a statutes source, then join on the bill number printed in the section's history line.

How do I find the legislators for an address? Geocode the address to latitude and longitude, then call /people.geo. It returns the state legislators and members of Congress currently representing that point. It does not cover governors or mayors.

Connect our US primary law database.
Every US statute, regulation, constitution, and executive order via REST, MCP or SQL. 5M+ sections, section-level citations, and links to the official source. Plus a free open dataset.
Updated October 5, 202618 min read

New legal AI guides, weekly.

Priyansh Khodiyar

Priyansh Khodiyar

Co-Founder & CTO

Priyansh leads engineering and AI at Vaquill AI: the pipelines that pull statutes, regulations and court rules from every US jurisdiction's official publisher, and the REST API, MCP server and open dataset that serve them.