USA Spending API in Python#

by Michael T. Moen

USA Spending Website: https://www.usaspending.gov/

USA Spending API: https://api.usaspending.gov/

These recipe examples were tested on November 9, 2023.

Setup#

First, import libraries needed to pull data from the API:

import requests                     # manages API requests
import matplotlib.pyplot as plt     # creates graphs
import numpy as np                  # used in the graphing of data

1. Get agency names and toptier codes#

To obtain data from the API, it is useful to first build a dictionary containing agency names and toptier codes, the latter of which will be used to access subagency data.

# defines endpoint for the tptier agency codes
toptier_agencies_endpoint = "/api/v2/references/toptier_agencies/"

# define URL for obtaining agency names and codes
toptier_agencies_url = f"https://api.usaspending.gov{toptier_agencies_endpoint}"

# imports data from URL as a JSON file
toptier_data = requests.get(toptier_agencies_url).json()["results"]

# display length
len(toptier_data)
108
# display data of first agency
toptier_data[0]
{'agency_id': 1146,
 'toptier_code': '310',
 'abbreviation': 'USAB',
 'agency_name': 'Access Board',
 'congressional_justification_url': 'https://www.access-board.gov/cj',
 'active_fy': '2023',
 'active_fq': '4',
 'outlay_amount': 8452843.81,
 'obligated_amount': 6790794.43,
 'budget_authority_amount': 11458971.28,
 'current_total_budget_authority_amount': 13615337201704.1,
 'percentage_of_total_budget_authority': 8.416222903804242e-07,
 'agency_slug': 'access-board'}

Now we can create a dictionary containing the agency names as keys and the toptier codes as the data.

# compile dictionary containing agency names and toptier codes
toptier_codes = {record["agency_name"] : record["toptier_code"] for record in toptier_data}

# display data
toptier_codes
{'Access Board': '310',
 'Administrative Conference of the U.S.': '302',
 'Advisory Council on Historic Preservation': '306',
 'African Development Foundation': '166',
 'Agency for International Development': '072',
 'American Battle Monuments Commission': '074',
 'Appalachian Regional Commission': '309',
 'Armed Forces Retirement Home': '084',
 'Barry Goldwater Scholarship and Excellence In Education Foundation': '313',
 "Commission for the Preservation of America's Heritage Abroad": '321',
 'Commission of Fine Arts': '323',
 'Commission on Civil Rights': '326',
 'Committee for Purchase from People Who Are Blind or Severely Disabled': '338',
 'Commodity Futures Trading Commission': '339',
 'Consumer Financial Protection Bureau': '581',
 'Consumer Product Safety Commission': '061',
 'Corporation for National and Community Service': '485',
 'Corps of Engineers - Civil Works': '096',
 'Council of the Inspectors General on Integrity and Efficiency': '542',
 'Court Services and Offender Supervision Agency': '9553',
 'Defense Nuclear Facilities Safety Board': '347',
 'Delta Regional Authority': '517',
 'Denali Commission': '513',
 'Department of Agriculture': '012',
 'Department of Commerce': '013',
 'Department of Defense': '097',
 'Department of Education': '091',
 'Department of Energy': '089',
 'Department of Health and Human Services': '075',
 'Department of Homeland Security': '070',
 'Department of Housing and Urban Development': '086',
 'Department of Justice': '015',
 'Department of Labor': '1601',
 'Department of State': '019',
 'Department of Transportation': '069',
 'Department of Veterans Affairs': '036',
 'Department of the Interior': '014',
 'Department of the Treasury': '020',
 'District of Columbia Courts': '349',
 'Election Assistance Commission': '525',
 'Environmental Protection Agency': '068',
 'Equal Employment Opportunity Commission': '045',
 'Executive Office of the President': '1100',
 'Export-Import Bank of the United States': '083',
 'Farm Credit System Insurance Corporation': '7802',
 'Federal Communications Commission': '027',
 'Federal Deposit Insurance Corporation': '051',
 'Federal Election Commission': '360',
 'Federal Financial Institutions Examination Council': '362',
 'Federal Labor Relations Authority': '054',
 'Federal Maritime Commission': '065',
 'Federal Mediation and Conciliation Service': '093',
 'Federal Mine Safety and Health Review Commission': '368',
 'Federal Permitting Improvement Steering Council': '473',
 'Federal Trade Commission': '029',
 'General Services Administration': '047',
 'Government Accountability Office': '005',
 'Gulf Coast Ecosystem Restoration Council': '471',
 'Harry S Truman Scholarship Foundation': '372',
 'Institute of Museum and Library Services': '474',
 'Inter-American Foundation': '164',
 'International Trade Commission': '034',
 'James Madison Memorial Fellowship Foundation': '381',
 'Japan-United States Friendship Commission': '382',
 'John F. Kennedy Center for the Performing Arts': '3301',
 'Marine Mammal Commission': '387',
 'Merit Systems Protection Board': '389',
 'Millennium Challenge Corporation': '524',
 'Morris K. Udall and Stewart L. Udall Foundation': '487',
 'National Aeronautics and Space Administration': '080',
 'National Archives and Records Administration': '088',
 'National Capital Planning Commission': '394',
 'National Council on Disability': '413',
 'National Credit Union Administration': '025',
 'National Endowment for the Arts': '417',
 'National Endowment for the Humanities': '418',
 'National Labor Relations Board': '420',
 'National Mediation Board': '421',
 'National Science Foundation': '049',
 'National Transportation Safety Board': '424',
 'Northern Border Regional Commission': '573',
 'Nuclear Regulatory Commission': '031',
 'Nuclear Waste Technical Review Board': '431',
 'Occupational Safety and Health Review Commission': '432',
 'Office of Government Ethics': '434',
 'Office of Navajo and Hopi Indian Relocation': '435',
 'Office of Personnel Management': '024',
 'Office of Special Counsel': '062',
 'Overseas Private Investment Corporation': '071',
 'Patient-Centered Outcomes Research Trust Fund': '579',
 'Peace Corps': '1125',
 'Pension Benefit Guaranty Corporation': '1602',
 'Presidio Trust': '512',
 'Privacy and Civil Liberties Oversight Board': '535',
 'Public Buildings Reform Board': '290',
 'Railroad Retirement Board': '060',
 'Securities and Exchange Commission': '050',
 'Selective Service System': '090',
 'Small Business Administration': '073',
 'Social Security Administration': '028',
 'Surface Transportation Board': '472',
 'U.S. Agency for Global Media': '514',
 'U.S. Interagency Council on Homelessness': '376',
 'U.S. International Development Finance Corporation': '077',
 'United States Chemical Safety Board': '510',
 'United States Court of Appeals for Veterans Claims': '345',
 'United States Trade and Development Agency': '1133',
 'Vietnam Education Foundation': '519'}

Finally, let’s print the toptier code for a particular agency using the toptier_codes dictionary we built. This will be useful when building URLs to view other data from the API.

toptier_codes["Department of Transportation"]
'069'

With this information, we can access subagency data using the toptier codes.

2. Retrieving Data from Subagencies#

The toptier_codes dictionary we created above contains every agency name in the API. For this example, we’ll look at the total obligations of each subagency of the Department of Defense.

# set this to the desired department
agency_name = 'Department of Defense'

subagency_url = f"https://api.usaspending.gov/api/v2/agency/{toptier_codes[agency_name]}/sub_agency/?fiscal_year=2023"

subagency_data = requests.get(subagency_url).json()["results"]

# define dictionary 'subagencies' with subagency names as keys and total_obligations as the data
subagencies = {data['name']: data['total_obligations'] for data in subagency_data}

subagencies
{'Department of the Navy': 123988049315.87,
 'Department of the Army': 87157860809.48,
 'Department of the Air Force': 77349881547.92,
 'Defense Logistics Agency': 38684897462.0,
 'Defense Health Agency': 17968814456.56,
 'Missile Defense Agency': 6474679176.62,
 'Defense Information Systems Agency': 5023021215.46,
 'USTRANSCOM': 4187533097.14,
 'U.S. Special Operations Command': 3499035349.0,
 'Washington Headquarters Services': 2699618136.56}

We’ll represent our data using a pie chart. To make the data easier to read, we can create an “Other” category to add the small projects to.

subagency_names = list(subagencies.keys())
subagency_obligations = list(subagencies.values())

smallest_percent_allowed = 0.015 # any sector under this threshold will be combined into an "Other" category in the pie chart
small_slices = 0 # the number of slices below the threshold above
agency_total_obligations = sum(subagencies.values()) # the total obligations of the agency

# count the number of small slices
small_slices = sum(1 for i in subagency_obligations if i < smallest_percent_allowed * agency_total_obligations)

# if there is more than one slice smaller than the threshold specified above, this combines those slices into an "Other" slice
if small_slices > 1:
    other_obligations = 0
    i = len(subagency_obligations)-1
    while i >= 0:
        if subagency_obligations[i] < smallest_percent_allowed * agency_total_obligations:
            other_obligations += subagency_obligations[i]
            subagency_names.pop(i)
            subagency_obligations.pop(i)
        i -= 1
    subagency_obligations.append(other_obligations)
    subagency_names.append("Other")

# configure color of pie chart
cmap = plt.get_cmap('YlGn_r')
colors = cmap(np.linspace(0.2, 0.8, len(subagency_obligations)))

fig, ax = plt.subplots()
plt.pie(subagency_obligations, labels=subagency_names, autopct='%1.1f%%', colors=colors)
plt.title("Subagency Obligations of the " + agency_name)

plt.show()
../../_images/3d42417a8507cf011c84d227877cb5aef3df1049855f1503409e820560ebc28f.png

3. Accessing Fiscal Data Per Year#

We can use the API to examine the annual budget of an agency from 2017 onward.

agency_name = "Department of Health and Human Services"

budgetary_resources_url = f"https://api.usaspending.gov/api/v2/agency/{toptier_codes[agency_name]}/budgetary_resources/"

budgetary_resources_data = requests.get(budgetary_resources_url).json()["agency_data_by_year"]

# number of year contained in the data
len(budgetary_resources_data)
8

Now we can create a dictionary to store the budgets using the years as keys.

# create budget_by_year dictionary with fiscal_year as the key and agency_total_obligated as the data
budget_by_year = {list_item['fiscal_year'] : list_item['agency_total_obligated'] for list_item in budgetary_resources_data}

# print results
budget_by_year
{2024: None,
 2023: 2241097923975.28,
 2022: 2452969781323.39,
 2021: 2355524286884.46,
 2020: 2198882208891.79,
 2019: 1814270463757.37,
 2018: 1679128003253.74,
 2017: 1646989531123.68}

The budget data for the current year (2023 at the time of writing) does not contain the entire annual budget, so let’s remove that entry from the data along with the 2024 entry.

budget_by_year.pop(2023)
budget_by_year.pop(2024)

budget_by_year
{2022: 2452969781323.39,
 2021: 2355524286884.46,
 2020: 2198882208891.79,
 2019: 1814270463757.37,
 2018: 1679128003253.74,
 2017: 1646989531123.68}

Now we’ll use matplotlib to create a bar graph for this information.

lists = sorted(budget_by_year.items())
years, budgets = zip(*lists)

# create scale for bar graph
min = sorted(list(budgets), key=float)[0]
exponent = len(str(int(min)))-1

budgets = [x/(10**exponent) for x in budgets]

fig, ax = plt.subplots()

# format the bar graph
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_color('#CCCCCC')
ax.tick_params(bottom=False, left=False)
ax.set_axisbelow(True)
ax.yaxis.grid(True, color='#DDDDDD')
ax.xaxis.grid(False)

plt.bar(years, budgets, color='#909090')
plt.title(agency_name + " Budgetary Resources")
plt.xlabel("Fiscal Year")
plt.ylabel(f"Total Budgetary Resources (* 10^{exponent})")
plt.show()
../../_images/dc1f49d7933c89541a4f106d291bda2b3f598d305233e59f4d4f2edb4bb94a69.png

4. Breaking Down Budget Categories#

We can use the API to view the breakdown the spending of a particular agency.

agency_name = "Department of the Interior"

obligations_by_category_url = f"https://api.usaspending.gov/api/v2/agency/{toptier_codes[agency_name]}/obligations_by_award_category/?fiscal_year=2023"

obligations_by_category_data = requests.get(obligations_by_category_url).json()

# Stores the total aggregated obligations for this particular agency
total_aggregated_amount = obligations_by_category_data["total_aggregated_amount"]

obligations_by_category_data = obligations_by_category_data["results"]

# print results
obligations_by_category_data
[{'category': 'contracts', 'aggregated_amount': 7802087246.07},
 {'category': 'direct_payments', 'aggregated_amount': 3311523401.72},
 {'category': 'grants', 'aggregated_amount': 7198549492.06},
 {'category': 'idvs', 'aggregated_amount': 3580835.81},
 {'category': 'loans', 'aggregated_amount': 0.0},
 {'category': 'other', 'aggregated_amount': 335594193.07}]

Let’s create a dictionary to store this data.

budget_breakdown = {}

# add entry to budget_breakdown dictionary only if the amount is greater than zero
for record in obligations_by_category_data:
    if record["aggregated_amount"] > 0:
        budget_breakdown[record["category"]] = record["aggregated_amount"]

budget_breakdown
{'contracts': 7802087246.07,
 'direct_payments': 3311523401.72,
 'grants': 7198549492.06,
 'idvs': 3580835.81,
 'other': 335594193.07}

In order to graph the data, we have to reformat and filter out some of our data:

smallest_percentage_allowed = 0.02 # any sector under this threshold will be combined into an "other" category

# count the number of small slices
small_slices = sum(1 for i in budget_breakdown.values() if smallest_percentage_allowed > i/total_aggregated_amount)

if small_slices > 1:
    
    keys_to_remove = []
    
    for key, value in budget_breakdown.items():
        if key == 'other' or smallest_percentage_allowed < value/total_aggregated_amount:
            continue
        if 'other' not in budget_breakdown.keys():
            budget_breakdown['other'] = 0
        budget_breakdown['other'] += value
        keys_to_remove.append(key)
    
    for key in keys_to_remove:
        budget_breakdown.pop(key)

budget_type = list(budget_breakdown.keys())
budget_amount = list(budget_breakdown.values())
budget_percentages = [x/total_aggregated_amount for x in budget_amount]

Now, we can graph the data with a stacked bar chart:

fig, ax = plt.subplots()
bottom = 1

for j, (height, label) in enumerate([*zip(budget_percentages, budget_type)]):
    bottom -= height
    bc = ax.bar(0, height=height, label=label, width=2, bottom=bottom, alpha=0.1 + 0.2 * j, align='center', color='C0')
    ax.bar_label(bc, labels=[f"{height:.1%}"], label_type='center')

ax.set_title(agency_name +' Budget Distribution')
ax.legend()
ax.axis('off')
ax.set_xlim(-6, 6)

plt.show()
../../_images/4c9df5a8e376684a7ab2752f184b4f01857723aebc73eddffd9eb49d3703679f.png

We can also plot this data as a pie chart.

lists = sorted(budget_breakdown.items())
types, budgets = zip(*lists)

fig, ax = plt.subplots()

# configure color of pie chart
cmap = plt.get_cmap('Blues')
colors = cmap(np.linspace(0.2, 0.8, len(budgets)))

plt.pie(budgets, labels=types, autopct='%1.1f%%', colors=colors)
plt.title(agency_name + " Budget Breakdown")
plt.show()
../../_images/7f32eed90f458b149ff63513b6ea24d855f4d5baca79217e62b9746b41de9942.png