USAspending API in Python#

by Adam M. Nguyen and Michael T. Moen

Please see the following resources for more information on API usage:

NOTE: Please see access details and rate limit requests for this API in the official documentation.

These recipe examples were tested on March 23, 2026.

Setup#

The following packages need to be installed into your environment to run the code examples in this tutorial. These packages can be installed with install.packages().

We load the libraries used in this tutorial below:

library(httr)
library(jsonlite)

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. The toptier codes serve as unique identifiers for each agency, so they are needed for accessing some of the data in the API.

BASE_URL <- "https://api.usaspending.gov/api/v2/"
toptier_agencies_endpoint <- "references/toptier_agencies/"

# Request data from the API
toptier_response <- GET(paste0(BASE_URL, toptier_agencies_endpoint))

# Response code of 200 indicates success
toptier_response$status_code
## [1] 200
# Extract data from response
toptier_df <- fromJSON(rawToChar(toptier_response$content))$results

# Print number of agencies
nrow(toptier_df)
## [1] 111
# Display the data for the first 5 results
head(toptier_df, n = 5)
##   agency_id toptier_code abbreviation
## 1      1525          247         AAHC
## 2      1146          310         USAB
## 3      1136          302         ACUS
## 4      1144          306         ACHP
## 5      1527          166        USADF
##                                        agency_name
## 1 400 Years of African-American History Commission
## 2                                     Access Board
## 3            Administrative Conference of the U.S.
## 4        Advisory Council on Historic Preservation
## 5                   African Development Foundation
##                                                                           congressional_justification_url
## 1                                                                                                    <NA>
## 2                                                                         https://www.access-board.gov/cj
## 3                                                                                 https://www.acus.gov/cj
## 4 https://www.achp.gov/sites/default/files/2021-06/ACHP%202022%20Budget%20Justification-final-5-10-21.pdf
## 5                                                                                https://www.usadf.gov/cj
##   active_fy active_fq outlay_amount obligated_amount budget_authority_amount
## 1      2026         2             0                0                       0
## 2      2026         2       2937972          2177662                 6834308
## 3      2026         2       1017478          1050524                 1215211
## 4      2026         2       2573530          4160479                17256104
## 5      2026         2       3159752          2599974                35589405
##   current_total_budget_authority_amount percentage_of_total_budget_authority
## 1                          1.328608e+13                         0.000000e+00
## 2                          1.328608e+13                         5.143962e-07
## 3                          1.328608e+13                         9.146500e-08
## 4                          1.328608e+13                         1.298811e-06
## 5                          1.328608e+13                         2.678699e-06
##                                        agency_slug
## 1 400-years-of-african-american-history-commission
## 2                                     access-board
## 3              administrative-conference-of-the-us
## 4        advisory-council-on-historic-preservation
## 5                   african-development-foundation

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

toptier_codes <- setNames(toptier_df$toptier_code, toptier_df$agency_name)

# Let's see the first 10 agencies and their toptier codes
head(toptier_codes, n = 10)
##                   400 Years of African-American History Commission 
##                                                              "247" 
##                                                       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"

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

# Look up toptier code of specific agency, in this case Department of Transportation
toptier_codes["Department of Transportation"]
## Department of Transportation 
##                        "069"

2. Retrieving Data from Subagencies#

The toptier_codes mapping 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.

# Specify agency name
agency_name <- 'Department of Defense'

# Assemble URL and send API request
dod_url <- paste0(BASE_URL, "agency/", toptier_codes[agency_name], "/sub_agency/")
params <- list(
  fiscal_year = 2024
)
dod_response <- GET(dod_url, query = params)

# Status code 200 indicates success
dod_response$status_code
## [1] 200
# Extract data from HTTP response
dod_df <- fromJSON(rawToChar(dod_response$content))$results

# Drop children column of data frame for display
dod_df <- dod_df[, names(dod_df) != "children"]

# Display the first 6 responses
head(dod_df)
##   abbreviation                        name total_obligations transaction_count
## 1          USN      Department of the Navy      135984528084            227615
## 2          USA      Department of the Army      108188048621            158007
## 3         USAF Department of the Air Force      101536586326            115687
## 4          DLA    Defense Logistics Agency       53148863110           3815438
## 5          DHA       Defense Health Agency       20196091028             15496
## 6          MDA      Missile Defense Agency        8463883396              3837
##   new_award_count
## 1           78953
## 2           53065
## 3           37597
## 4         3639087
## 5            4299
## 6             356

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

obligations <- dod_df$total_obligations
names(obligations) <- dod_df$name

total_obligations <- sum(obligations)

threshold <- 0.015
small <- obligations < threshold * total_obligations

if (sum(small) > 1) {
  obligations <- c(obligations[!small], Other = sum(obligations[small]))
}

par(mar = c(5, 4, 4, 8))

pie(
  obligations,
  # Format data labels to billions of dollars
  labels = paste0("$", formatC(obligations / 1e9, format = "f", digits = 2), "B"),
  col = rainbow(length(obligations)),
  main = paste("Subagency Obligations of the", agency_name)
)

legend(
  "bottomright",
  inset = c(-0.25, -0.25),
  xpd = TRUE,
  legend = names(obligations),
  fill = rainbow(length(obligations)),
  cex = 0.8
)

3. Accessing Fiscal Data Per Year#

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

# Specify agency name
agency_name <- "Department of Health and Human Services"

# Assemble URL and send API request
hhs_url <- paste0(BASE_URL, "agency/", toptier_codes[agency_name], "/budgetary_resources/")
hhs_response <- GET(hhs_url)

# Status code 200 indicates success
hhs_response$status_code
## [1] 200
# Extract data into a data frame
hhs_df <- fromJSON(rawToChar(hhs_response$content))$agency_data_by_year

# Drop agency_obligation_by_period column for display purpose
hhs_df <- hhs_df[, names(hhs_df) != "agency_obligation_by_period"]

# Print the first few rows of the data frame
head(hhs_df, n = 5)
##   fiscal_year agency_budgetary_resources agency_total_obligated
## 1        2026               2.604643e+12           1.188028e+12
## 2        2025               3.129725e+12           2.794350e+12
## 3        2024               2.864471e+12           2.518648e+12
## 4        2023               2.841385e+12           2.475674e+12
## 5        2022               2.735931e+12           2.452970e+12
##   agency_total_outlayed total_budgetary_resources
## 1          1.000654e+12              1.328608e+13
## 2          2.722170e+12              1.326370e+13
## 3          2.474861e+12              1.224855e+13
## 4          2.423435e+12              1.188986e+13
## 5          2.386741e+12              1.140981e+13
# Drop the most recent year, since the data is not complete
hhs_df <- hhs_df[hhs_df$fiscal_year != max(hhs_df$fiscal_year), ]
head(hhs_df, n = 5)
##   fiscal_year agency_budgetary_resources agency_total_obligated
## 2        2025               3.129725e+12           2.794350e+12
## 3        2024               2.864471e+12           2.518648e+12
## 4        2023               2.841385e+12           2.475674e+12
## 5        2022               2.735931e+12           2.452970e+12
## 6        2021               2.660484e+12           2.355524e+12
##   agency_total_outlayed total_budgetary_resources
## 2          2.722170e+12              1.326370e+13
## 3          2.474861e+12              1.224855e+13
## 4          2.423435e+12              1.188986e+13
## 5          2.386741e+12              1.140981e+13
## 6          2.168495e+12              1.221910e+13

Now, we can create a bar plot of the retrieved data.

# Extract and name values for plotting
values <- hhs_df$agency_total_obligated / 1e12
names(values) <- hhs_df$fiscal_year

# Sort by fiscal year
values <- values[order(names(values))]

y_breaks <- pretty(c(0, values), n = 6)
y_lim    <- c(0, max(y_breaks))

barplot(
  values,
  xlab = "Fiscal Year",
  ylab = "Total Obligations (Trillions of $)",
  main = "Agency Total Obligations for HHS",
  col = "aquamarine4",
  border = "white",
  space = 0,
  cex.axis = 0.8,
  cex.names = 0.8,
  ylim = y_lim
)

4. Breaking Down Award Categories#

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

# Specify agency name
agency_name <- "Department of the Interior"

# Define parameters and make API request
doi_url <- paste0(BASE_URL, "agency/", toptier_codes[agency_name],
                  "/obligations_by_award_category")
params <- list(
  fiscal_year = 2023
)
doi_response <- GET(doi_url, query = params)

# Status code 200 indicates success
doi_response$status_code
## [1] 200
# Extract data from API response
doi_df <- fromJSON(rawToChar(doi_response$content))$results

# Print results
doi_df
##          category aggregated_amount
## 1       contracts        7641526477
## 2 direct_payments        3435334040
## 3          grants        6893917437
## 4            idvs           3579836
## 5           loans                 0
## 6           other         338985935
# Clean data frame values
doi_df <- doi_df[doi_df$aggregated_amount > 0, ]
doi_df
##          category aggregated_amount
## 1       contracts        7641526477
## 2 direct_payments        3435334040
## 3          grants        6893917437
## 4            idvs           3579836
## 6           other         338985935
values <- doi_df$aggregated_amount
names(values) <- doi_df$category

total_aggregated_amount <- sum(values)

par(mar = c(4, 4, 4, 5))

pie(
  values,
  # Format data labels to billions of dollars
  labels = paste0("$", formatC(values / 1e9, format = "f", digits = 2), "B"),
  col = rainbow(length(values)),
  main = paste("Award Categories of the", agency_name)
)

legend(
  "bottomright",
  xpd = TRUE,
  legend = names(values),
  fill = rainbow(length(values)),
  cex = 0.8
)