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:
- 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".
-
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 apsc_codesfilter and atime_period. - 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.
- 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:
-
categoriespicks from 13 groups, including "IT & Cyber", "Telecom", "Construction", "Medical & Health", "Professional Services" and "Defense & Aerospace". Leave it empty to compare all 13. -
agenciesPerCategoryreturns that manyagency_driverrows, ranked by dollar change vs a year ago. -
topRecipientsPerCategoryreturns that manytop_recipientrows. -
newEntrantLookbackPages: 1compares 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 to0to 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 justgrowthYoYPct. - Recipients are listed per UEI. Raytheon appears twice because two of its registered entities are listed separately.
-
Not every row has every field.
agencyappears only onagency_driverrows, andrecipienton recipient rows.growthYoYPctisnullwhen there was no spending a year ago (the trend is thennew/insufficient base). Code for that (see Step 3). -
concentratedistruewhen 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.
- In the Apify console, click Save as a new task with your categories (and
awardingAgencyif you sell to one department). - 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
- How to Get Daily SAM.gov Contract Opportunity Alerts in Slack: act on the opportunities once you've picked a market.
- Expiring Federal Contracts: Recompete Finder: contracts in your category that end in the next 6 to 18 months.
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.
Top comments (0)