The SAM.gov Opportunities API v2 accepts a naics query parameter. The documentation implies it will filter results to matching NAICS codes. It doesn’t.

What actually happens

GET https://api.sam.gov/prod/opportunities/v2/search?api_key=...&naics=541511&limit=100

Returns opportunities with NAICS codes 541330, 561210, 711510, and anything else currently open — not just 541511. The filter is parsed, no error is returned, but the result set is unfiltered.

This appears to be a persistent bug in the v2 API. It isn’t mentioned in the documentation.

Fix: filter client-side

Fetch without the naics parameter and apply your own filter on the naicsCode field in the response:

import urllib.request, json

SAM_KEY = "your_api_key"
TARGET_NAICS = {'541511', '541512', '541513', '541519', '518210'}

url = f"https://api.sam.gov/prod/opportunities/v2/search?api_key={SAM_KEY}&limit=100&postedFrom=01/01/2026"
req = urllib.request.Request(url, headers={"Accept": "application/json"})
data = json.loads(urllib.request.urlopen(req, timeout=30).read())

all_opps = data.get("opportunitiesData", [])
filtered = [o for o in all_opps if str(o.get("naicsCode", "")) in TARGET_NAICS]

print(f"Total returned: {len(all_opps)}, matching NAICS: {len(filtered)}")

The naicsCode field in the response payload is accurate — the problem is only with the server-side query filter.

Agency and deadline fields are also unreliable

While we’re here: fullParentPathName (the agency name) is frequently empty even when the opportunity clearly belongs to a specific agency. responseDeadLine is missing on pre-solicitations.

If you need accurate agency/deadline data, enrich after the fact by querying for the specific notice ID:

def enrich_opp(opp_id, api_key):
    url = f"https://api.sam.gov/prod/opportunities/v2/search?api_key={api_key}&noticeid={opp_id}&limit=1"
    req = urllib.request.Request(url, headers={"Accept": "application/json"})
    data = json.loads(urllib.request.urlopen(req, timeout=10).read())
    opps = data.get("opportunitiesData", [])
    if not opps:
        return {}
    o = opps[0]
    return {
        "agency": o.get("fullParentPathName") or o.get("departmentName", ""),
        "deadline": o.get("responseDeadLine") or o.get("archiveDate", ""),
        "naics": str(o.get("naicsCode", "")),
    }

Stay under 10 requests/second. A time.sleep(0.15) between calls keeps you safely within the rate limit.