BLS API

Updated · ◆ Intermediate ·

Bureau of Labor Statistics (BLS) API with Python

The BLS Public Data API allows machine access to an enormous and incredibly useful set of U.S. economic data. With python, it is easy to integrate BLS data into projects, research, and graphics without the need to manually find, download, and combine files.

This notebook offers two examples of using python to request economic data from the BLS API. The first example requests the labor force participation rate from the BLS v1 API. The second example requests unemployment rates by race/origin, and shows a method for requesting multiple series over a longer period of time using the v2 API.


Background

BLS

The U.S. Bureau of Labor Statistics is part of the Department of Labor. You can read more about BLS here.

The Bureau of Labor Statistics of the U.S. Department of Labor is the principal Federal agency responsible for measuring labor market activity, working conditions, and price changes in the economy.

API

BLS-data-related tasks that are exploratory, repetitive, or need to be well documented, can make use of Application Programming Interface (API) to save time. The API allows users to request data with specific parameters, such as the BLS series ID, dates, or preferred format. Data returned can be fed into pandas or other programs for further calculations and analysis.

Python

The examples use Python 3.x with the requests and pandas packages.


Example 1: Single series from API v1

Import Libraries and Request Data

The version 1 API does not require registration. The series ID is appended directly to the URL path. You can find series IDs using the BLS data site search tools. Here we request the civilian labor force participation rate (LNS11300000).

In[1]:

import requests
import pandas as pd

# BLS API v1: series ID goes in the URL path
url = 'https://api.bls.gov/publicAPI/v1/timeseries/data/LNS11300000'
r = requests.get(url).json()
Explore the Response

The API returns a nested dictionary. The actual data lives at r['Results']['series'][0]['data']—a list of dictionaries, one per month, with the most recent month first.

In[2]:

data = r['Results']['series'][0]['data']
print(data[0])
{'year': '2025', 'period': 'M12', 'periodName': 'December', 'latest': 'true', 'value': '62.4', 'footnotes': [{}]}
Read into Pandas

We create a DataFrame directly from the list of dictionaries, then construct a proper datetime index from the year and period fields.

In[3]:

df = pd.DataFrame(data)

The period field uses the format M01 through M12. We strip the M prefix to build a date string. BLS uses - for missing values, so we replace those with None before converting to float.

In[4]:

# Combine year and period to create datetime column
df['date'] = pd.to_datetime(df['year'] + '-' + df.period.str[1:] + '-01')
# Extract footnote text
df['note'] = [i[0].get('text', '') for i in df['footnotes']]

# Reorganize data; BLS uses '-' for missing values
df = df.set_index('date')[['value', 'note']].sort_index().replace('-', None)
df['value'] = df.value.astype(float)
df.tail(3)

Out[4]:

value note
date
2025-10-01 NaN Data unavailable due to the 2025 lapse in appropriations.
2025-11-01 62.5
2025-12-01 62.4
Plot the Results

Plot the monthly values with a horizontal line at the period mean to show the overall trend.

In[5]:

ax = df['value'].plot(title='Labor Force Participation Rate')
ax.axhline(df['value'].mean(), color='orange', label='mean')
ax.legend();

Out[5]:

Pandas Plot Output

The v1 API is simple but limited in the number of requests and years returned. For more complex requests, the v2 API shown in example 2 is preferable.


Example 2: Requesting multiple series and specific dates

The second example uses the BLS API v2 (which requires free registration) to request more than one series at the same time. The version 2 API has a higher daily query limit, allows more years and series to be returned in each query, and allows some additional options such as requesting data in percent change rather than level. See difference between v1 and v2. The API key is stored in a local config.py file; see the Getting Started guide for details.

Parameters

The v2 API uses a POST request instead of GET—we send a JSON body specifying which series, years, and API key we want. The requests library's json= parameter handles the serialization and headers automatically. See: What is the difference between POST and GET?

In[6]:

import config  # .py file with bls_key = 'API key here'

url = 'https://api.bls.gov/publicAPI/v2/timeseries/data/'

# Series stored as a dictionary
series_dict = {
    'LNS14000003': 'White',
    'LNS14000006': 'Black',
    'LNS14000009': 'Hispanic'
}

# Post request with json= (handles serialization and headers)
results = requests.post(url, json={
    "seriesid": list(series_dict.keys()),
    "startyear": "2008",
    "endyear": "2017",
    "registrationkey": config.bls_key
}).json()['Results']['series']
Process the Data

The API returns each series in the same format, so we loop through them to build a DataFrame. The data arrives in reverse chronological order, so we reverse it with iloc[::-1].

In[7]:

# Date index from first series
date_list = [f"{i['year']}-{i['period'][1:]}-01" for i in results[0]['data']]

# Build a DataFrame column per series
df = pd.DataFrame()
for s in results:
    df[series_dict[s['seriesID']]] = pd.Series(
        index=pd.to_datetime(date_list),
        data=[i['value'] for i in s['data']]
    ).astype(float).iloc[::-1]

df.tail()

Out[7]:

Black Hispanic White
2017-03-01 8.0 5.1 3.9
2017-04-01 7.9 5.2 3.8
2017-05-01 7.5 5.2 3.7
2017-06-01 7.1 4.8 3.8
2017-07-01 7.4 5.1 3.8
Plot the Results

Plot all three series to compare how unemployment rates moved during and after the Great Recession.

In[8]:

df.plot(title='Unemployment Rates by Race or Origin')

Out[8]:

Pandas Plot Output

Further Resources