Use the free USAspending API to compare federal contract spending by category with a year ago, and see which agencies and vendors are behind the change.

Written with AI assistance and reviewed by the FedSignals Data team; we maintain these tools.

"Is federal spending in my market growing?" sounds simple. USAspending.gov has every federal contract obligation, and its API is free with no API key. But answering the question takes some work:

  1. Choose your categories. USAspending classifies contracts with thousands of PSC (product and service) codes, so you need to group them into markets like "IT & Cyber" or "Construction".
  2. Make three queries per category. You need the current window, the window just before it, and the same window a year ago, using POST /api/v2/search/spending_by_category/awarding_agency/ with a psc_codes filter and a time_period.
  3. Leave a lag. Department of Defense contract data is published with a 90-day delay, so a window that ends today makes DoD-heavy categories look like they're collapsing.
  4. Break down the change. Rank agencies by dollar change, then pull the top recipients and check which of them weren't in the top list last year.

The official API docs and the UA Libraries cookbook are good if you want to build this yourself. We built the USAspending Scraper: Federal Spending Trends by Category Actor on Apify to run those steps and return one row per signal. This tutorial walks through it.

What you'll build

  • Spending for each category in the latest complete 90-day window, compared with the window before it and the same window a year ago.
  • A rising, flat or falling label based on your threshold.
  • The agencies driving each change, the top recipients, and "new entrants" (vendors in this window's top 100 that weren't in the top list for the previous 12 months).
  • A monthly report in Python or Slack.

Step 1: Run it once by hand

Open apify.com/fedsignals/federal-spending-signals and click Try for free. This is the exact input we used for this article:

{ "categories": ["Telecom"], "agenciesPerCategory": 3, "topRecipientsPerCategory": 3, "newEntrantLookbackPages": 1 } 

What each setting does:

  • categories picks from 13 groups, including "IT & Cyber", "Telecom", "Construction", "Medical & Health", "Professional Services" and "Defense & Aerospace". Leave it empty to compare all 13.
  • agenciesPerCategory returns that many agency_driver rows, ranked by dollar change vs a year ago.
  • topRecipientsPerCategory returns that many top_recipient rows.
  • newEntrantLookbackPages: 1 compares this window's top 100 recipients with the top 100 of the previous 12 months. Each extra page adds 100 more to that comparison list. Set it to 0 to skip new entrants.

The defaults are a 90-day window (windowDays), ending 95 days before today (lagDays) to allow for the DoD delay, with a ±15% threshold for rising or falling (growthThresholdPct). You can also limit everything to one buyer with awardingAgency, for example Department of Veterans Affairs or VA.

Step 2: Look at the output

Our run on Oct 29, 2026 took about 4 seconds. Its status read 10 signal rows; top rising: Telecom. The first row is the category trend:

{ "signalType": "category_trend", "category": "Telecom", "currentAmount": 7942892920.56, "yearAgoAmount": 4185976973.62, "priorWindowAmount": 3872037505.96, "currency": "USD", "growthYoYPct": 89.8, "growthVsPriorPct": 105.1, "trend": "rising", "windows": { "current": ["2026-04-28", "2026-07-26"], "prior": ["2026-01-28", "2026-04-27"], "yearAgo": ["2025-04-29", "2025-07-27"] }, "topRecipient": "RAYTHEON COMPANY", "topRecipientSharePct": 24.5, "concentrated": false, "risingRank": 1 } 

Telecom contract obligations were $7.94B from Apr 28 to Jul 26, 2026, up 89.8% from $4.19B in the same window of 2025. The other nine rows show where that came from:

Signal Who Current window Note
agency_driver Department of Defense $7.54B +87.5% YoY
agency_driver Department of Homeland Security $152.7M +656.3% YoY
agency_driver Department of Justice $53.5M +737% YoY
top_recipient Raytheon Company (UEI ending NG1) $1.94B #1
top_recipient Raytheon Company (UEI ending 598) $1.56B #2
top_recipient Lockheed Martin Corporation $1.33B #3
new_entrant SAAB Inc $43.5M not in prior 12 months' top 100
new_entrant BI2 Technologies, LLC $25.1M not in prior 12 months' top 100
new_entrant Crown Innovations, Inc. $24.9M not in prior 12 months' top 100

A few things to read carefully:

  • One agency can drive the whole trend. DoD is 95% of this category. The small agencies' percentages look dramatic but are tens of millions of dollars, so check changeAmount, not just growthYoYPct.
  • Recipients are listed per UEI. Raytheon appears twice because two of its registered entities are listed separately.
  • Not every row has every field. agency appears only on agency_driver rows, and recipient on recipient rows. growthYoYPct is null when there was no spending a year ago (the trend is then new/insufficient base). Code for that (see Step 3).
  • concentrated is true when the top recipient has 25% or more of the category. That's a hint that a category is dominated by one incumbent.

The run also saves a summary in its key-value store (record SUMMARY). It holds the three windows, the categories ranked by year-over-year growth (rankedByYoY) and the row count.

Step 3: Call it from Python

This compares three categories and prints the trend with its top two agency drivers. It reads your Apify token from an environment variable (never paste it into code). maxTotalChargeUsd caps the run's charges.

import os import requests ACTOR = "fedsignals~federal-spending-signals" URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items" run_input = { "categories": ["IT & Cyber", "Construction", "Medical & Health"], "agenciesPerCategory": 2, "topRecipientsPerCategory": 0, "newEntrantLookbackPages": 0, } resp = requests.post( URL, params={"maxTotalChargeUsd": 0.15}, headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"}, json=run_input, timeout=300, ) resp.raise_for_status() def pct(x): return f"{x:+.1f}%" if x is not None else "n/a" for n in resp.json(): if n["signalType"] == "category_trend": print(f"{n['category']}: ${n['currentAmount'] / 1e9:,.2f}B, " f"YoY {pct(n.get('growthYoYPct'))}, vs prior {pct(n.get('growthVsPriorPct'))} ({n['trend']})") elif n["signalType"] == "agency_driver": print(f" {n.get('agency') or 'unknown agency'}: change ${(n.get('changeAmount') or 0) / 1e6:+,.1f}M " f"({pct(n.get('growthYoYPct'))})") 

Each category prints one trend line, followed by its agency drivers. These are aggregates, not new records, so this Actor has no onlyNew mode. Every run returns the latest comparison.

Step 4: Schedule it monthly

The window rolls forward each day, but the totals move slowly, so a monthly run is enough.

  1. In the Apify console, click Save as a new task with your categories (and awardingAgency if you sell to one department).
  2. Go to Schedules → Create new, use the cron expression 0 8 1 * * (8:00 on the 1st of each month), and attach the task.

Step 5: Send the report to Slack

Add a webhook for ACTOR.RUN.SUCCEEDED that points at a small function you host. The payload includes resource.defaultDatasetId:

import os import requests def handler(payload: dict) -> None: dataset_id = payload["resource"]["defaultDatasetId"] items = requests.get( f"https://api.apify.com/v2/datasets/{dataset_id}/items", headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"}, timeout=60, ).json() trends = [n for n in items if n.get("signalType") == "category_trend"] if not trends: return trends.sort(key=lambda n: n.get("growthYoYPct") if n.get("growthYoYPct") is not None else float("-inf"), reverse=True) lines = ["*Federal contract spending by category, vs a year ago*"] for n in trends: g = n.get("growthYoYPct") growth = f"{g:+.1f}%" if g is not None else "n/a" lines.append(f"• {n['category']}: ${(n.get('currentAmount') or 0) / 1e9:,.2f}B ({growth}, {n.get('trend')})") new = [n for n in items if n.get("signalType") == "new_entrant"] if new: lines.append("New entrants: " + ", ".join(f"{n.get('recipient')} ({n.get('category')})" for n in new[:10])) requests.post(os.environ["SLACK_WEBHOOK_URL"], json={"text": "\n".join(lines)}, timeout=30) 

How to use the signals

  • Choose where to spend business development time. A rising category at an agency you already know is a better bet than a flat one.
  • Find partners. New entrants just won meaningful work in your category, and many need teaming partners or subcontractors.
  • See which contracts come up next. Pair this with our Recompete Finder to list contracts in a growing category that end in the next 6 to 18 months.

What it costs

The Actor uses pay-per-event pricing: $0.01 per signal row, plus Apify's $0.00005 run-start fee. The 10-row Telecom run above costs about $0.10. The Python example returns 9 rows (3 trends plus 6 agency drivers), about $0.09. With every category and the default drill-downs, a run returns 150 to 320 rows ($1.50 to $3.20). Use the per-category settings and maxTotalChargeUsd to keep runs small.

FAQ

Does the USAspending API need a key?
No. It's public, keyless and free. The Actor waits between calls (minRequestIntervalSec, 0.4 s by default) to be polite. You only need a free Apify account.

Why does the current window end three months ago?
DoD contract data is published with a 90-day delay. The default lagDays: 95 keeps the year-over-year comparison fair. Set it lower if you only care about civilian agencies.

What's in each category?
Each category is a group of PSC code prefixes. For example, "IT & Cyber" covers the IT services (D) and IT products and software (7A to 7K) PSC families. The Actor's README describes the mapping.

Are these obligations or outlays?
Obligations on contract awards (award types A to D), as reported to USAspending.

Is this the same as the USAspending website's charts?
It uses the same public API. The difference is the three-window comparison, the lag and the driver breakdown, done in one call.

Related

Last updated: Nov 4, 2026 (tested against Actor build 0.1.14).

FedSignals Data builds small public-data monitors on Apify. FedSignals is not affiliated with, or endorsed by, the U.S. government or USAspending.gov. Questions or feature requests: fedsignals.alerts@gmail.com.