USAspending
A Ruby client for the USAspending.gov REST API v2. Access federal award data, agency spending, geographic breakdowns, recipient profiles, and more.
No API key required. The USAspending.gov API is fully public.
Installation
Add to your Gemfile:
gem 'usaspending-rb'Then bundle install, or install directly:
gem install usaspending-rbQuick Start
require 'usaspending'
# Search for contracts
response = USAspending.awards.search(
filters: { award_type_codes: %w[A B C D] },
limit: 10
)
response.each { |award| puts award['Recipient Name'] }Usage
Award Search
response = USAspending.awards.search(
filters: {
award_type_codes: %w[A B C D],
place_of_performance_locations: [
{ country: 'USA', state: 'VA', district: '08' }
]
},
fields: ['Award ID', 'Recipient Name', 'Award Amount'],
sort: 'Award Amount',
limit: 25
)
response.results # => [{ "Award ID" => "...", ... }, ...]
response.size # => 25
response.total_count # => 1234
response.next_page? # => truePaginate All Results
Uses cursor pagination for reliability with large result sets:
USAspending.awards.search_all(
filters: { award_type_codes: %w[A B C D] },
limit: 100
) do |page|
page.each { |award| save_to_db(award) }
endAward Detail
award = USAspending.awards.find('CONT_AWD_N0018923C0001_9700_-NONE-_-NONE-')
award['description']
award['total_obligation']
# Related data
USAspending.awards.accounts(award_id: 'CONT_AWD_...')
USAspending.awards.funding(award_id: 'CONT_AWD_...')
USAspending.awards.transaction_count('CONT_AWD_...')Geographic Spending
# State-level breakdown
response = USAspending.spending.by_geography(
scope: 'place_of_performance',
geo_layer: 'state',
filters: { fiscal_years: [2025] }
)
# Congressional district convenience method
response = USAspending.spending.for_district(
state_abbr: 'VA',
district: '08',
fiscal_years: [2025]
)Category Breakdown
# By NAICS industry code
USAspending.spending.by_category(
category: 'naics',
filters: { fiscal_years: [2025] },
limit: 25
)
# Or use Search directly for any of the 15 categories
USAspending.search.spending_by_awarding_agency(filters: { fiscal_years: [2025] })
USAspending.search.spending_by_recipient(filters: { fiscal_years: [2025] })
USAspending.search.spending_by_cfda(filters: { fiscal_years: [2025] })
USAspending.search.spending_by_naics(filters: { fiscal_years: [2025] })
USAspending.search.spending_by_psc(filters: { fiscal_years: [2025] })
# ... and 10 more category methodsAgency Data
# List all top-tier agencies
agencies = USAspending.agency.list
# Agency overview (e.g. Treasury = "020")
treasury = USAspending.agency.overview('020', fiscal_year: 2025)
# Drill down
USAspending.agency.sub_agencies('020', fiscal_year: 2025)
USAspending.agency.federal_accounts('020', fiscal_year: 2025)
USAspending.agency.budget_function('020', fiscal_year: 2025)
USAspending.agency.obligations_by_award_category('020', fiscal_year: 2025)
USAspending.agency.budgetary_resources('020')Recipient Data
# Search recipients
results = USAspending.recipient.search('Lockheed Martin')
# Recipient profile
profile = USAspending.recipient.find('abc123-hash-value')
# State-level data
states = USAspending.recipient.states
virginia = USAspending.recipient.state('51') # FIPS codeAutocomplete
USAspending.autocomplete.location('22201', geo_layer: 'zip_code')
USAspending.autocomplete.naics('541')
USAspending.autocomplete.recipient('Lockheed')
USAspending.autocomplete.awarding_agency('Treasury')
USAspending.autocomplete.city('Arlington', state_code: 'VA')
USAspending.autocomplete.psc('R425')Reference Data
USAspending.references.toptier_agencies
USAspending.references.assistance_listings # CFDA programs
USAspending.references.def_codes # Disaster emergency fund codes
USAspending.references.naics(depth: 2) # NAICS industry codes
USAspending.references.psc_tree # Product service codes
USAspending.references.data_dictionary
USAspending.references.glossary
USAspending.references.submission_periodsDisaster / Emergency Fund Spending
# COVID spending overview
USAspending.disaster.overview(def_codes: %w[L M N])
# Drill down by entity
USAspending.disaster.agency_spending(def_codes: %w[L M])
USAspending.disaster.recipient_spending(def_codes: %w[L])
USAspending.disaster.cfda_spending(def_codes: %w[L])
# Geographic breakdown
USAspending.disaster.spending_by_geography(def_codes: %w[L], geo_layer: 'state')Spending Explorer
USAspending.spending_explorer.explore(
type: 'budget_function',
filters: { fy: '2025', quarter: '1' }
)Downloads
# Generate a CSV download
response = USAspending.download.awards(
filters: { award_type_codes: %w[A], fiscal_years: [2025] }
)
file_name = response['file_name']
# Check status
USAspending.download.status(file_name)
# Bulk downloads
USAspending.bulk_download.awards(filters: { fiscal_years: [2025] })Federal Accounts
USAspending.federal_accounts.list
USAspending.federal_accounts.find('020-0001') # by account code
USAspending.federal_accounts.fiscal_year_snapshot(1234) # by numeric IDAdditional Resources
# Transactions for an award
USAspending.transactions.list(award_id: 'CONT_AWD_...')
# Subawards
USAspending.subawards.list(award_id: 'CONT_AWD_...')
# IDV (Indefinite Delivery Vehicles)
USAspending.idv.amounts('IDV_AWD_...')
USAspending.idv.awards(award_id: 'IDV_AWD_...')
# Reporting
USAspending.reporting.agencies_overview(fiscal_year: 2025, fiscal_period: 6)
# Financial data
USAspending.financial_balances.agencies(funding_agency_identifier: '020', fiscal_year: 2025)
USAspending.budget_functions.listConfiguration
USAspending.configure do |config|
config.timeout = 60 # Request timeout in seconds (default: 30)
config.retries = 5 # Max retries on 429/5xx (default: 3)
config.logger = Logger.new($stdout) # Optional request logging
endRails Initializer
# config/initializers/usaspending.rb
USAspending.configure do |config|
config.timeout = 30
config.retries = 3
config.logger = Rails.logger if Rails.env.development?
endPer-Instance Client
For different configurations in the same process:
client = USAspending::Client.new(custom_config)
client.awards.search(filters: { ... })
client.agency.overview('020')Response Object
All API calls return a USAspending::Response:
response = USAspending.awards.search(filters: { ... })
response.results # Array of result hashes
response.each { |r| ... } # Enumerable — supports map, select, first, etc.
response.size # Results on this page
response.empty? # No results?
response.total_count # Total matching records (not just this page)
response.next_page? # More pages available?
response.last_record_unique_id # Cursor for next page
response.success? # HTTP 2xx?
response['key'] # Direct hash access
response.dig('nested', 'key')
response.raw # Raw parsed JSON body
response.to_h # Always returns a Hash
response.to_json # JSON stringError Handling
begin
USAspending.awards.find('INVALID')
rescue USAspending::BadRequestError => e
puts "Bad request: #{e.status} — #{e.body}"
rescue USAspending::NotFoundError => e
puts "Not found: #{e.status}"
rescue USAspending::UnprocessableEntityError => e
puts "Invalid filters: #{e.body}"
rescue USAspending::RateLimitError
puts 'Rate limited — retries exhausted'
rescue USAspending::ServerError => e
puts "Server error: #{e.status}"
rescue USAspending::ConnectionError => e
puts "Network issue: #{e.message}"
endError hierarchy:
USAspending::Error
├── HttpError
│ ├── ClientError
│ │ ├── BadRequestError (400)
│ │ ├── NotFoundError (404)
│ │ ├── UnprocessableEntityError (422)
│ │ └── RateLimitError (429)
│ └── ServerError (500+)
└── ConnectionError (timeouts, DNS, refused)
All HttpError subclasses expose .status and .body.
Development
git clone https://github.com/thepublictab/usaspending-rb.git
cd usaspending-rb
bundle install
bundle exec rspec # Run tests
bundle exec rubocop # Lint
bundle exec yard # Generate docs
bundle exec rake # Default: runs rspecRequirements
- Ruby >= 3.1
- Faraday ~> 2.0
License
MIT License. See LICENSE.txt.