If you’re building anything that pulls contract award data from USASpending.gov and you need to filter by HHS sub-agency (NIH, FDA, CDC, CMS, AHRQ), you’ve probably hit this: the subtier name filter returns nothing.

What doesn’t work

body = {
    "filters": {
        "agencies": [
            {"type": "awarding", "tier": "subtier", "name": "Food and Drug Administration"}
        ]
    }
}

Returns {"results": [], "page_metadata": {"total": 0}} — every time, for every HHS component, regardless of how you spell the name. The API accepts the request without error.

This isn’t a typo issue. We tried "FDA", "Food and Drug Administration", and the exact strings that appear in USASpending’s own agency endpoint. Zero results every time.

What works

Query at the toptier level (Department of Health and Human Services) and then identify the sub-agency by parsing a 4-digit code out of the generated_internal_id field that comes back in each award record.

body = {
    "filters": {
        "agencies": [
            {
                "type": "awarding",
                "tier": "toptier",
                "name": "Department of Health and Human Services"
            }
        ],
        "award_type_codes": ["A", "B", "C", "D"],
    },
    "fields": ["Award ID", "Recipient Name", "Award Amount", "End Date",
               "Description", "generated_internal_id"],
    "page": 1,
    "limit": 100,
    "sort": "End Date",
    "order": "desc"
}

Then parse the sub-agency from generated_internal_id:

SUBTIER_CODE_MAP = {
    "7523": "CDC",
    "7524": "FDA",
    "7528": "AHRQ",
    "7529": "NIH",
    "7530": "CMS",
}

def agency_from_gid(gid: str) -> str:
    """Parse 4-digit subtier code from generated_internal_id."""
    # Format: CONT_AWD_{award_id_parts}_{4digit_subtier_code}_{...}
    parts = gid.split("_")
    code = next((p for p in parts if len(p) == 4 and p.isdigit()), None)
    return SUBTIER_CODE_MAP.get(code, "hhs-other")

for award in results:
    gid = award.get("generated_internal_id", "")
    award["_agency"] = agency_from_gid(gid)

Pagination warning

USASpending pagination is slow — each page takes 10–30 seconds. With 5 agencies and multiple pages, a full run can take 30+ minutes. For most use cases, page 1 is sufficient: you get the 100 most recent awards sorted by end date, which is what you want for recompete signal tracking.

# Always set --max-time; skip and continue on timeout
curl -sk --max-time 30 -X POST \
  "https://api.usaspending.gov/api/v2/search/spending_by_award/" \
  -H "Content-Type: application/json" \
  -d "$body"

Don’t loop through hasNext. You’ll time out before you finish, and the marginal signal in pages 2+ doesn’t justify it.

ONC note

ONC (Office of the National Coordinator for Health IT) has no direct contract awards under IT professional services NAICS codes. ONC contracts in this space flow through AHRQ (subtier code 7528). If you’re tracking ONC recompetes, look for AHRQ awards with ONC-relevant descriptions.