Legiscan API#
The Legiscan API provides access to legislative data, such as bills, sponsors, hearing information, etc, from all 50 states. To use the Legiscan API, you’d first need to make an account on Legiscan, and request an API key. The key (which we will incorporate into our API call) gives you authentication to access the data on Legiscan.
After getting our API key, we can create our API call. The first step is import our libraries that we need in order to use the API. Then, we can construct our API call, putting together the root, key and query.
import requests
import pandas as pd
import time
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.0.1 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.
If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
Traceback (most recent call last): File "/Users/fcalado/.conda/envs/jb/lib/python3.11/runpy.py", line 198, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/runpy.py", line 88, in _run_code
exec(code, run_globals)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel_launcher.py", line 18, in <module>
app.launch_new_instance()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/traitlets/config/application.py", line 1075, in launch_instance
app.start()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelapp.py", line 739, in start
self.io_loop.start()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/tornado/platform/asyncio.py", line 205, in start
self.asyncio_loop.run_forever()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/base_events.py", line 608, in run_forever
self._run_once()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/base_events.py", line 1936, in _run_once
handle._run()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/events.py", line 84, in _run
self._context.run(self._callback, *self._args)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 545, in dispatch_queue
await self.process_one()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 534, in process_one
await dispatch(*args)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 437, in dispatch_shell
await result
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/ipkernel.py", line 362, in execute_request
await super().execute_request(stream, ident, parent)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 778, in execute_request
reply_content = await reply_content
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/ipkernel.py", line 449, in do_execute
res = shell.run_cell(
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/zmqshell.py", line 549, in run_cell
return super().run_cell(*args, **kwargs)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3075, in run_cell
result = self._run_cell(
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3130, in _run_cell
result = runner(coro)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
coro.send(None)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3334, in run_cell_async
has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3517, in run_ast_nodes
if await self.run_code(code, result, async_=asy):
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3577, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "/var/folders/z6/8hbw1n6n3dq2jdw3_zjysj5sws21gh/T/ipykernel_71574/4032102518.py", line 2, in <module>
import pandas as pd
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/__init__.py", line 26, in <module>
from pandas.compat import (
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/compat/__init__.py", line 27, in <module>
from pandas.compat.pyarrow import (
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/compat/pyarrow.py", line 8, in <module>
import pyarrow as pa
File "/Users/fcalado/.local/lib/python3.11/site-packages/pyarrow/__init__.py", line 65, in <module>
import pyarrow.lib as _lib
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: _ARRAY_API not found
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.0.1 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.
If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
Traceback (most recent call last): File "/Users/fcalado/.conda/envs/jb/lib/python3.11/runpy.py", line 198, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/runpy.py", line 88, in _run_code
exec(code, run_globals)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel_launcher.py", line 18, in <module>
app.launch_new_instance()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/traitlets/config/application.py", line 1075, in launch_instance
app.start()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelapp.py", line 739, in start
self.io_loop.start()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/tornado/platform/asyncio.py", line 205, in start
self.asyncio_loop.run_forever()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/base_events.py", line 608, in run_forever
self._run_once()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/base_events.py", line 1936, in _run_once
handle._run()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/asyncio/events.py", line 84, in _run
self._context.run(self._callback, *self._args)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 545, in dispatch_queue
await self.process_one()
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 534, in process_one
await dispatch(*args)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 437, in dispatch_shell
await result
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/ipkernel.py", line 362, in execute_request
await super().execute_request(stream, ident, parent)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/kernelbase.py", line 778, in execute_request
reply_content = await reply_content
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/ipkernel.py", line 449, in do_execute
res = shell.run_cell(
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/ipykernel/zmqshell.py", line 549, in run_cell
return super().run_cell(*args, **kwargs)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3075, in run_cell
result = self._run_cell(
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3130, in _run_cell
result = runner(coro)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
coro.send(None)
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3334, in run_cell_async
has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3517, in run_ast_nodes
if await self.run_code(code, result, async_=asy):
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3577, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "/var/folders/z6/8hbw1n6n3dq2jdw3_zjysj5sws21gh/T/ipykernel_71574/4032102518.py", line 2, in <module>
import pandas as pd
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/__init__.py", line 49, in <module>
from pandas.core.api import (
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/core/api.py", line 9, in <module>
from pandas.core.dtypes.dtypes import (
File "/Users/fcalado/.conda/envs/jb/lib/python3.11/site-packages/pandas/core/dtypes/dtypes.py", line 24, in <module>
from pandas._libs import (
File "/Users/fcalado/.local/lib/python3.11/site-packages/pyarrow/__init__.py", line 65, in <module>
import pyarrow.lib as _lib
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: _ARRAY_API not found
# the components of the API call, which make up the "request" variable
url = 'https://api.legiscan.com/?key='
key = # insert your key here
page = 1
request = requests.get(url + key + '&op=getSearch&state=ALL&query=transgender' + '&page=' + str(page))
# to print out the full URL, and we can paste in the browser to get
# an interactive look at the raw results
print(url + key + '&op=getSearch&state=ALL&query=transgender' + '&page=' + str(page))
Cell In[2], line 3
key = # insert your key here
^
SyntaxError: invalid syntax
Now we can make the API call, using the “request” object that we created above. From there, we call the .json() method, to navigate through the results, which are in json format.
Accessing items in json involves using brackets to indicate the keys.
# get the page_total and the count from the request summary
page_total = request.json()['searchresult']['summary']['page_total']
count = request.json()['searchresult']['summary']['count']
print('Page total: ' + str(page_total) + '\n' + 'Total results: ' + str(count))
Page total: 13
Total results: 604
Now we write a few loops. The first one gathers all the data from each page of the results. The second loop parses that data into a dataframe object (a tabular or spreadsheet format) so we can examine it and eventually save it to a csv file.
# request the additional pages of the query by adding 1 to the 'page'
# parameter until it reaches the page_total. Store each page of
# requests in a list. Wait 3 seconds between each request to avoid
# overloading the API
pages = []
for i in range(page_total):
page = i + 1
request = requests.get(url + key + '&op=getSearch&state=ALL&query=transgender' + '&page=' + str(page))
time.sleep(3)
pages.append(request.json())
# for each page of the request, parse the results and add them to a
# dataframe. each page is a json file with individual results labeled
# '0' through '49' and # 'summary' nested under searchresult. Ignore
# the summary and use pandas.concat to add each of the results from
# the request in a dataframe
df = pd.DataFrame()
for page in pages:
results = page['searchresult']
# if the page has no results, skip it
for i in range(50):
if str(i) in results:
df = pd.concat([df, pd.DataFrame(results[str(i)], index=[i])])
else:
continue
Now we have our data in a tabular structure, thanks to the DataFrame that we got from a different library called pandas. This DataFrame format enables us to examine our data in a spreadsheet.
df
| relevance | state | bill_number | bill_id | change_hash | url | text_url | research_url | last_action_date | last_action | title | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 100 | UT | HB0316 | 1819064 | bb27e8c4d929c9331af7b02dc6d81348 | https://legiscan.com/UT/bill/HB0316/2024 | https://legiscan.com/UT/text/HB0316/2024 | https://legiscan.com/UT/research/HB0316/2024 | 2024-02-12 | Senate/ 1st reading (Introduced) in Senate Rul... | Inmate Assignment Amendments |
| 1 | 99 | DC | B25-0460 | 1778035 | e96fc947b1b4170adf7a3fe91291a61b | https://legiscan.com/DC/bill/B25-0460/2023 | https://legiscan.com/DC/text/B25-0460/2023 | https://legiscan.com/DC/research/B25-0460/2023 | 2023-09-22 | Notice of Intent to Act on B25-0460 Published ... | Transgender and Gender-Diverse Mortality and F... |
| 2 | 99 | DC | CER25-0143 | 1782702 | 9aff2f06c9f9b38306f8f3a8e83183c8 | https://legiscan.com/DC/bill/CER25-0143/2023 | https://legiscan.com/DC/text/CER25-0143/2023 | https://legiscan.com/DC/research/CER25-0143/2023 | 2023-11-24 | Resolution ACR25-0141, Effective from Nov 07, ... | Transgender Day of Remembrance Recognition Res... |
| 3 | 99 | VT | JRH004 | 1751508 | bfb4a15ca9ece1f262e6ed5759e192ba | https://legiscan.com/VT/bill/JRH004/2023 | https://legiscan.com/VT/text/JRH004/2023 | https://legiscan.com/VT/research/JRH004/2023 | 2023-04-07 | Senate Message, adopted in concurrence | Joint resolution recognizing March 31, 2023 as... |
| 4 | 99 | US | HR886 | 1784730 | 15a2a26d5c91782333a2b37fc8083154 | https://legiscan.com/US/bill/HR886/2023 | https://legiscan.com/US/text/HR886/2023 | https://legiscan.com/US/research/HR886/2023 | 2023-11-21 | Referred to the House Committee on the Judiciary. | Supporting the goals and principles of Transge... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 49 | 6 | US | HB2670 | 1757049 | ccfa8fba0550b39bb71af841122e2132 | https://legiscan.com/US/bill/HB2670/2023 | https://legiscan.com/US/text/HB2670/2023 | https://legiscan.com/US/research/HB2670/2023 | 2023-12-22 | Became Public Law No: 118-31. | CONVENE Act of 2023 Sensible Classification Ac... |
| 0 | 4 | MI | HB4437 | 1757146 | 604fc33c04c0b4d89cce5b8bd0d51893 | https://legiscan.com/MI/bill/HB4437/2023 | https://legiscan.com/MI/text/HB4437/2023 | https://legiscan.com/MI/research/HB4437/2023 | 2023-09-06 | Disapproved Line Item(s) Re-referred To Commit... | Appropriations: omnibus; appropriations for mu... |
| 1 | 4 | NY | S04007 | 1690727 | 1579358405b6c681a7bd5ed500b7ac14 | https://legiscan.com/NY/bill/S04007/2023 | https://legiscan.com/NY/text/S04007/2023 | https://legiscan.com/NY/research/S04007/2023 | 2023-05-03 | SIGNED CHAP.57 | Enacts into law major components of legislatio... |
| 2 | 3 | NY | S04004 | 1690688 | 30e29d6956eec2d3ff19a58c35ad73f8 | https://legiscan.com/NY/bill/S04004/2023 | https://legiscan.com/NY/text/S04004/2023 | https://legiscan.com/NY/research/S04004/2023 | 2023-05-01 | SUBSTITUTED BY A3004D | Makes appropriations for the support of govern... |
| 3 | 3 | NY | A03004 | 1690608 | b862da143af26ca5a2997011fc933673 | https://legiscan.com/NY/bill/A03004/2023 | https://legiscan.com/NY/text/A03004/2023 | https://legiscan.com/NY/research/A03004/2023 | 2023-05-12 | thru line veto memo.36 | Makes appropriations for the support of govern... |
604 rows Ă— 11 columns
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 604 entries, 0 to 3
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 relevance 604 non-null int64
1 state 604 non-null object
2 bill_number 604 non-null object
3 bill_id 604 non-null int64
4 change_hash 604 non-null object
5 url 604 non-null object
6 text_url 604 non-null object
7 research_url 604 non-null object
8 last_action_date 604 non-null object
9 last_action 604 non-null object
10 title 604 non-null object
dtypes: int64(2), object(9)
memory usage: 56.6+ KB
df.to_csv('legiscan_api_results.csv')
That’s it!