USASpending API Guide (2026)

A practical reference for the USASpending.gov API. Covers every useful endpoint, the quirks that cost new users a day or two, the field-naming rules that bite, and how to match USASpending data back to SAM.gov. Free to use, no registration, no API key, covers federal spending back to 2008.

Who this is for: developers pulling historical federal contract data; analysts sizing markets by NAICS or agency; researchers tracing sub-award relationships; anyone trying to cross-reference a SAM.gov award number against the authoritative obligation record.

What USASpending actually is

USASpending.gov is the US Treasury's public transparency site for all federal spending. Under the hood it pulls from FPDS-NG (Federal Procurement Data System) for contracts, from agency grants systems for grants and loans, and from FSRS reporting for sub-awards. It exposes this data through a JSON REST API that anyone can hit without an account.

What it covers:

What it is not:

On this page:

Access and rate limits

USASpending is genuinely free. No account, no API key, no CAPTCHA. Base URL: https://api.usaspending.gov. Every request is either a GET for a single resource or a POST with a JSON filter body for searches. There is no header-based auth. CORS is open; you can hit it from a browser, curl, or server-side.

Rate limits: not officially published. Tested 20 concurrent workers running 60 search requests back-to-back (April 2026): zero 429s, zero 504s, median latency ~1.6s. In practice the API tolerates whatever pace you throw at it in reasonable amounts. Still back off exponentially on any transient 429/502/503/504.

The five useful endpoints

POST /api/v2/search/spending_by_award/

The workhorse. Paginated award-level search, 100 records max per page. Fast (typically 300-600ms). Used for finding awards by recipient name, NAICS, agency, time period, award type.

Critical quirk: this endpoint returns NULL for NAICS Code, NAICS Description, PIID, Total Obligation, Date Signed, Award Type, Solicitation Identifier, Extent Competed, and Type of Contract Pricing even when you request them in the fields array. These fields exist on the underlying record but the search endpoint does not project them. This is not a bug in your code. To get them, fetch each award individually from /awards/{generated_internal_id}/.

Fields that do come back populated: Award ID, Recipient Name, Recipient UEI, Award Amount, Start Date, End Date, Awarding Agency, Description, recipient_id, generated_internal_id, Total Outlays.

Example:

curl -X POST "https://api.usaspending.gov/api/v2/search/spending_by_award/" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "recipient_search_text": ["Lockheed Martin"],
      "award_type_codes": ["A","B","C","D"],
      "time_period": [{"start_date":"2024-01-01","end_date":"2026-12-31"}]
    },
    "fields": ["Award ID","Recipient Name","Recipient UEI","Award Amount","Start Date","Awarding Agency"],
    "sort": "Award Amount",
    "order": "desc",
    "page": 1,
    "limit": 100
  }'

Required: sort and order. Omit them and you get a 400 back with a list of valid sort keys.

Field names are capitalized and spaced on this endpoint: "Recipient UEI", not recipient_uei. Get this wrong and you silently lose the field without an error.

GET /api/v2/awards/{generated_internal_id}/

The full record. Returns 26+ top-level fields plus nested latest_transaction_contract_data with 69 contract fields. Use it whenever you need anything spending_by_award returned NULL for.

ID format: CONT_AWD_{piid}_{agency_toptier_code}_{parent_piid}_{parent_agency_code}. You get it from generated_internal_id on any search response.

What it returns that search does not:

Example:

curl "https://api.usaspending.gov/api/v2/awards/CONT_AWD_36C25026P0208_3600_-NONE-_-NONE-/"

Speed: ~1.5-2 seconds per call in April 2026 testing (median 1.59s over 20 sequential calls, p95 2.07s). Parallel calls don't speed up; five workers also got ~1.6s median. Enrichment at scale (hundreds of awards) adds up fast; use the bulk download endpoint for large pulls.

POST /api/v2/search/spending_by_transaction/

Transaction-level search (each contract modification and action separately, not rolled-up awards). Less commonly needed, but it has one very useful property.

This is the one paginated search that returns NAICS. If you need NAICS in a paginated result set without fetching each award individually, use spending_by_transaction instead of spending_by_award. Valid fields include naics_code, naics_description, product_or_service_code, product_or_service_description, pop_city_name, pop_state_code.

Downside: you get one row per modification, so a long-running contract shows up many times. Aggregate by internal_id or generated_unique_award_id client-side.

POST /api/v2/search/spending_by_category/{category}/

Aggregations. Not a list of awards; a list of categories (recipients, NAICS codes, agencies, states, etc.) with total obligated dollars. Use for market sizing, top-N lists, agency mapping.

Categories you care about:

Example (top 20 recipients in NAICS 541512, last 2 years):

curl -X POST "https://api.usaspending.gov/api/v2/search/spending_by_category/recipient/" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "naics_codes": {"require": ["541512"]},
      "time_period": [{"start_date":"2024-01-01","end_date":"2026-12-31"}]
    },
    "limit": 20
  }'

Quirk: the response returns a uei field alongside recipient_id. You can skip the recipient-profile lookup step if all you wanted was the UEI.

POST /api/v2/download/awards/ (async bulk)

For when the paginated endpoints are too slow or too thin. Generates a CSV or ZIP file with every field, up to 500,000 records per job.

Workflow:

  1. POST /api/v2/download/awards/ with your filters. Returns status_url, file_name, file_url, and a download_request echoing your query.
  2. Poll status_url until the job moves from running to finished.
  3. Download the CSV/ZIP from file_url.

Time to generate scales with filter breadth. Tiny filters (one recipient, one month) finish in under 10 seconds. Mid-size pulls (one toptier agency, one week) can take 3+ minutes. Broad historical pulls can be longer still.

Right tool for: one-time backfills, agency-wide exports, historical research where you need the full field set. Wrong tool for: daily incremental sync (too slow, too heavy).

The quirks that cost you a day

Field names switch between endpoints.
The search endpoints use capitalized, spaced field names ("Award ID", "Recipient Name", "Recipient UEI", "Award Amount") in the fields array, and the same keys show up in the response. But recipient_id, uei, generated_internal_id on the same response are lowercase snake_case. Other endpoints (the detail endpoint, the category endpoints) use lowercase snake_case throughout. Read an example response before guessing.
spending_by_award returns NULL for the fields you actually want.
NAICS, PIID, obligation, date signed, extent competed. All NULL. See the endpoint section above. You cannot fix this by changing your fields array; the API does not populate them on this endpoint. Fetch awards individually for those fields or use spending_by_transaction.
Sort and order are required on spending_by_award.
Missing them returns a 400 with a list of valid sort keys. Common pick: "sort": "Award Amount", "order": "desc".
recipient_id is not UEI.
recipient_id is a USASpending-internal hash like 6cf5fb1b-4988-d087-5dc1-70939d8fc6c4-C. Three possible suffixes expose different aggregation views of the same entity: -C (child or direct entity, always present), -P (parent company view, only exists when the entity has a corporate parent), -R (recognized aggregate across related identifiers). A given UEI maps to exactly one -C recipient_id, but a corporate parent may have many. For stable cross-system matching use Recipient UEI or uei, not recipient_id.
Pagination is page + limit, not offset.
Pass "page": 1 (1-indexed), not "offset": 0. Max 100 per page on most endpoints. Check page_metadata.hasNext in the response to know when you are done.
Award type codes are opaque letters and digits.
A, B, C, D = contracts (BPA, purchase order, definitive contract, etc.). 02, 03, 04, 05 = grants. Omit award_type_codes and you will pull everything including loans and grants. Almost always you want ["A","B","C","D"].
Time period dates are strings, not ISO datetimes.
Format: "start_date":"2024-01-01". Earliest allowed is 2007-10-01 (start of FY 2008). The API returns a warning in the messages array if you request earlier.
The search endpoints are slow at high offsets.
Beyond page ~50 (5,000 records), response time climbs significantly. For bulk, use the download endpoint.
Sort field must appear in your fields array.
If you pass "sort": "Award Amount" but omit "Award Amount" from fields, the API returns a 400: "Sort value 'Award Amount' not found in requested fields". Always include the sort field among the fields you request.
Sub-award endpoints are a parallel universe.
Searching sub-awards uses the same spending_by_award endpoint but adds "subawards": true to the request body. Valid fields switch to sub-award variants: "Sub-Award ID", "Sub-Award Amount", prime_award_generated_internal_id. Sub-award data comes from FSRS self-reporting by prime contractors and is incomplete (some primes do not report; accuracy varies).

Matching USASpending to SAM.gov

If you already have SAM.gov award data and want to enrich it, or vice versa, the match key is:

SAM.gov fieldUSASpending fieldNotes
award_number piid Primary match key. This is the contract number. Both systems should have the same string.
award_uei_sam recipient_uei / Recipient UEI Recipient match. UEI is stable across both systems.
solicitation_number solicitation_identifier Works in principle. In practice, NULL on spending_by_award results and only populated on the detail endpoint.

To build a generated_internal_id from a SAM award_number: the format is CONT_AWD_{award_number}_{agency_toptier_code}_-NONE-_-NONE-. Easier route: search spending_by_award with a keyword filter equal to the award number.

Match lag: awards that appear in SAM this week will not appear in USASpending for 2-4 weeks. Do not treat an initial miss as a data quality issue; retry the match after a month.

The 23,000 contractors SAM award notices miss

SAM.gov's Award Notice feed is what contracting officers publish on the public opportunity portal. USASpending draws from FPDS, which captures more contract actions. Measured against our ingested SAM Award Notices and USASpending corpus in April 2026:

Why the USASpending-only delta exists:

If completeness matters (market research, full competitive mapping, due diligence), you need both sources.

What USASpending adds over SAM award notices

Fields you already get from SAM Award Notices (no reason to fetch from USASpending):

Fields you only get from USASpending (worth the enrichment):

Working code examples

1. Look up a recipient's UEI by company name

import requests

def find_uei(company_name):
    r = requests.post(
        "https://api.usaspending.gov/api/v2/search/spending_by_award/",
        json={
            "filters": {
                "recipient_search_text": [company_name],
                "award_type_codes": ["A","B","C","D"],
                "time_period": [{"start_date":"2022-01-01","end_date":"2026-12-31"}],
            },
            # "Award Amount" must be in fields because we sort by it
            "fields": ["Recipient Name", "Recipient UEI", "Award Amount"],
            "sort": "Award Amount",
            "order": "desc",
            "limit": 5,
        },
    )
    r.raise_for_status()
    return [(row["Recipient Name"], row["Recipient UEI"]) for row in r.json()["results"]]

print(find_uei("Lockheed Martin"))

2. Pull a contractor's full award history

Two steps because spending_by_award is thin. Step 1 gets the list. Step 2 enriches each award with NAICS, PSC, obligation, business categories.

import requests
import time

UEI = "FYHNA5WC8XD7"  # example

# Step 1: paginate spending_by_award
awards = []
page = 1
while True:
    r = requests.post(
        "https://api.usaspending.gov/api/v2/search/spending_by_award/",
        json={
            "filters": {
                "recipient_search_text": [UEI],
                "award_type_codes": ["A","B","C","D"],
                "time_period": [{"start_date":"2008-10-01","end_date":"2026-12-31"}],
            },
            "fields": ["Award ID","Recipient UEI","Award Amount","Start Date","Awarding Agency"],
            "sort": "Award Amount",
            "order": "desc",
            "page": page,
            "limit": 100,
        },
    )
    data = r.json()
    awards.extend(data["results"])
    if not data["page_metadata"]["hasNext"]:
        break
    page += 1
    time.sleep(0.3)

# Step 2: enrich with detail endpoint
enriched = []
for a in awards[:50]:
    gid = a["generated_internal_id"]
    d = requests.get(f"https://api.usaspending.gov/api/v2/awards/{gid}/").json()
    enriched.append({
        **a,
        "naics": d.get("latest_transaction_contract_data", {}).get("naics"),
        "psc": d.get("latest_transaction_contract_data", {}).get("product_or_service_code"),
        "total_obligation": d.get("total_obligation"),
        "business_categories": d.get("recipient", {}).get("business_categories", []),
        "parent_recipient_name": d.get("recipient", {}).get("parent_recipient_name"),
        "extent_competed": d.get("latest_transaction_contract_data", {}).get("extent_competed"),
    })
    time.sleep(0.25)

print(f"Fetched {len(enriched)} enriched awards")

3. Size a market: top 20 contractors in a NAICS

import requests

def top_recipients_in_naics(naics, start, end, limit=20):
    r = requests.post(
        "https://api.usaspending.gov/api/v2/search/spending_by_category/recipient/",
        json={
            "filters": {
                "naics_codes": {"require": [naics]},
                "time_period": [{"start_date": start, "end_date": end}],
            },
            "limit": limit,
        },
    )
    return r.json()["results"]

for row in top_recipients_in_naics("541512", "2024-01-01", "2026-12-31"):
    print(f"${row['amount']/1e6:>10,.1f}M  {row['uei']}  {row['name']}")

4. Match a SAM award_number to USASpending

import requests

def find_in_usaspending(sam_award_number):
    r = requests.post(
        "https://api.usaspending.gov/api/v2/search/spending_by_award/",
        json={
            "filters": {
                "keywords": [sam_award_number],
                "award_type_codes": ["A","B","C","D"],
                "time_period": [{"start_date":"2020-01-01","end_date":"2026-12-31"}],
            },
            "fields": ["Award ID","Recipient Name","Recipient UEI","Award Amount"],
            "sort": "Award Amount",
            "order": "desc",
            "limit": 5,
        },
    )
    results = r.json()["results"]
    if not results:
        return None  # May just be too new (2-4 week USASpending lag)
    return results[0]["generated_internal_id"]

# Then fetch full detail
gid = find_in_usaspending("FA8620-25-C-0012")
if gid:
    detail = requests.get(f"https://api.usaspending.gov/api/v2/awards/{gid}/").json()

When to use USASpending vs alternatives

Your need Best source Why
Active solicitations, current opportunities SAM.gov API USASpending is historical. SAM has the live opportunity feed.
Award history for a company (pre-2024) USASpending Goes back to FY 2008. SAM.gov's public award notice feed is much shorter.
Award history for a company (2024+) SAM.gov or GovCon API USASpending lags 2-4 weeks. Both other options are within 24 hours.
Business categories, parent company, extent competed USASpending (detail endpoint) Not in SAM.gov's Award Notice feed.
Total obligation vs award ceiling USASpending (detail endpoint) Only source for actual dollars obligated on a contract.
Sub-awards USASpending (subaward search) Only public source. FSRS self-reported so expect gaps.
Fast programmatic contractor lookup (name -> aggregated profile) GovCon API Pro Bundle USASpending requires many calls for aggregates; GovCon API has a precomputed profile endpoint. $39/month.
Peer/competitor analysis (similar companies) GovCon API Pro Bundle No direct USASpending endpoint for this. Possible via manual NAICS intersection (5-10 API calls).
Raw FPDS transaction-level data FPDS-NG ATOM feed USASpending's transaction endpoint is easier to use but FPDS has the full raw record.

FAQ

Do I need to register for USASpending?
No. The API is completely open. No account, no key, no CORS restrictions.

What's the actual rate limit?
Not published. In an April 2026 test, 20 concurrent workers issuing 60 search requests back-to-back hit zero 429s or 504s; median latency held at ~1.6 seconds. Treat that as the floor, not a guarantee. Back off exponentially on any transient 429/502/503/504.

Why does the same company appear with multiple recipient_id values?
USASpending assigns recipient_id at the parent, child, and recognized-entity levels. A corporate parent has one recipient_id; each subsidiary has its own. The -P / -C / -R suffix tells you which level. Match on Recipient UEI for stable identity across the hierarchy.

Why is my SAM.gov contract missing from USASpending?
Most likely it was awarded in the last 2-4 weeks and has not propagated yet. Retry after a month. If it is still missing, the contract action may not have been reported to FPDS (rare but happens with certain agency systems).

Can I get all award data for an agency in one request?
Use the bulk download endpoint (POST /api/v2/download/awards/) with an agency filter. The paginated search endpoints top out at 10,000 records per query (practical limit).

Does USASpending include classified contracts?
No. Classified contracts and certain national security work do not appear in FPDS and therefore not in USASpending.

Why do obligation totals not match the award amount?
Award amount is usually the ceiling (the most the government could spend). Total obligation is what has actually been committed as of the record's date. They converge as a contract completes; they diverge most for IDIQ, cost-plus, and long-running option-year contracts.

Does the API support GraphQL?
No. JSON REST only. The search endpoints use POST with a filter body; resource endpoints use GET.

Can I search by solicitation_identifier?
The filter accepts it, but since spending_by_award returns NULL for this field, your results rarely match. Match on keywords with the award number (PIID) instead, then read solicitation_identifier from the detail endpoint.

Is there an official Python client?
No. Plain requests works fine. Community clients exist on PyPI (search "usaspending") but most are incomplete.

USASpending is the correct free source for historical federal contract data. For live opportunities, use SAM.gov. For programmatic contractor research with peer analysis and aggregated profiles, our GovCon API collapses multi-step USASpending workflows into one call.

Last updated: April 2026 · How to research a federal contractor · SAM.gov API guide · Questions

Related guides

Official resources

Last updated: April 2026