SAM.GOV API

Author

J Markley

Utilization of sam.gov API

sam.gov API description

The sam.gov API is availble for businesses registered on sam.gov and is only accessible using an assigned API key. There are tens of thousands of opportunities posted on sam.gov and each one has 27 variables which each contain information about the contracts. Quickly sorting through these contracts is an invaluable tool for government contractors.

Libraries Used

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(jsonlite)

Attaching package: 'jsonlite'

The following object is masked from 'package:purrr':

    flatten
library(magrittr)

Attaching package: 'magrittr'

The following object is masked from 'package:purrr':

    set_names

The following object is masked from 'package:tidyr':

    extract
library(httr)

Constructing the API URL

The function requires several inputs including postedFrom and postedTo which designates the dates during which the contract opportunities have been posted - these dates must be within one year of each other.

As you can see, I have included quite a few variables to narrow down the results.

sam_api_GET_url <- 
  function(api_key, postedFrom, postedTo,
           rdlfrom, rdlto, ptype, ncode, ccode) {

    sam_endpoint <- 
      "https://api.sam.gov/opportunities/v2/search"

    api_key <- paste("?api_key=", api_key, sep = "")
    
    postedFrom <- paste("&postedFrom=", postedFrom, sep = "")
    
    postedTo <- paste("&postedTo=", postedTo, sep = "")
    
    rdlfrom <- paste("&rdlfrom=", rdlfrom, sep = "")
    
    rdlto <- paste("&rdlto=", rdlto, sep = "")
    
    ptype <- paste("&ptype=", ptype, sep = "")

    ncode <- paste("&ncode=", ncode, sep ="")
    
    ccode <- paste("&ccode=", ccode, sep = "")
    
    limit <- ("&limit=1000")
    
    sam_api_GET <- paste(sam_endpoint,
                         api_key,
                         postedFrom,
                         postedTo,
                         rdlfrom,
                         rdlto,
                         ptype,
                         ncode,
                         ccode,
                         limit,
                         sep="")
    
    return(sam_api_GET)
  }

Downloading the data

After a little poking around, I determined the location of the data frame in the series opportunitiesData

sam_api_GET_df <- 
  function(api_key, postedFrom, postedTo, rdlfrom, rdlto, ptype, ncode, ccode) {
    
    sam_api_url <- 
      sam_api_GET_url(api_key, postedFrom, postedTo, 
                      rdlfrom, rdlto, ptype, ncode, ccode)
    
    sam_df <- 
      sam_api_url %>% 
      GET() %>% 
      content(as = "text",
              encoding = "UTF-8") %>% 
      fromJSON() %>% 
      use_series(opportunitiesData)
    
    return(sam_df)
  }

User Inputs

There were a total of 8 inputs I used (3 of which were required) to filter the data.

sam541519 <- 
  sam_api_GET_df(api_key = "BUSINESS_API_KEY", 
                 postedFrom = "11/15/2024", 
                 postedTo = "11/4/2025",
                 rdlfrom = "10/05/2025",
                 rdlto = "06/06/2026",
                 ptype = "o", 
                 ncode = "541519",
                 ccode = "")

Handling the JSON file, creating a .csv

I created a new data frame for each set of contracts with different NAICS (ncode) values.

The downloaded data frame had to be flattened before I could create the .csv using this code:

sam_flat <- jsonlite::flatten(sam541519)

I then created the .csv:

write_csv(sam_flat, “sam.csv”)

The output is a data frame that looks like this:

sam_data <- read_csv("https://myxavier-my.sharepoint.com/:x:/g/personal/markleyj3_xavier_edu/EYqtnuYSlt5KhAB37B9xY-8BsxjVBrJVMnH-ldPSbd7tJQ?download=1")
Rows: 15 Columns: 36
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (26): noticeId, title, solicitationNumber, fullParentPathName, fullPare...
dbl   (2): naicsCode, placeOfPerformance.zip
lgl   (5): naicsCodes, pointOfContact, additionalInfoLink, links, resourceLinks
dttm  (1): responseDeadLine
date  (2): postedDate, archiveDate

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The titles of the 15 contracts pulled with the selected parameters are

sam_data$title
 [1] "ESS Preventive Maintenance and Inspection Services"                                                                          
 [2] "CSO - USTRANSCOM Information Technology (IT) Enterprise Modernization (ITEM) Solutions"                                      
 [3] "H-CTSS Equipment Refresh"                                                                                                    
 [4] "Energy Procurement Support Tool (EPST)"                                                                                      
 [5] "Data Modernization Section Support"                                                                                          
 [6] "Next Generation (NextGen) Passport Personalization Printers Support"                                                         
 [7] "Cyber Range for Training for the AO"                                                                                         
 [8] "Screening Information Request (SIR) for the Strategic Sourcing for the Acquisition of Various Supplies and Equipment (SAVES)"
 [9] "FDIC's National Conference Room Audio Visual BOA"                                                                            
[10] "MEBS"                                                                                                                        
[11] "DA10--VISN 10 Paperless Medication Guides  VHA VISN 10 Pharmacy"                                                             
[12] "Congress KnowWho for Salesforce Subscription Maintenance"                                                                    
[13] "DA10--RTLS Battery Management Service Contract NY Harbor"                                                                    
[14] "VA Modernized Messaging Software as a Service (SaaS)"                                                                        
[15] "RFI for XML Gateways"                                                                                                        

Conclusion

NAICS codes are used to categorize contract work types and are the easiest way to filter contracts. I have already begun using the API to gather information about contract opportunities that may be of interest to my business. I have already proven that this method is more effective and efficient than using the contract search engine availble on sam.gov, and that the contract data from the API is more robust and provides better information than its web-available brother.