TL;DR: A Congressional Research Service report states that when a federal contract is expected to exceed $25,000, the agency posts the solicitation on SAM.gov. There are three ways to watch those postings without refreshing the site every morning: SAM.gov's own saved search email alerts (free, but thin), a Python script that polls SAM.gov's public search API (free, real routing control), or a scheduled monitor that handles dedup and alerting with no infrastructure (our SAM.gov New-Opportunity Monitor is built for this). All three methods are below, with working code.
I once watched a small IT services firm lose a bid they were perfect for because someone found the solicitation with three days left on the response window. Not three days after it posted. Three days before responses were due. The notice had been sitting on SAM.gov for six weeks.
That is the entire problem in one story. The federal government posts thousands of contract opportunities, and the difference between winning and never knowing a contract existed is usually just timing. Small contractors and boutique govcon shops cannot afford a full time person refreshing SAM.gov every morning. So they check when they remember, which means they miss things.
Here are three ways to fix that, from simplest to fully automated.
Before we start: the Delta Tools shortcut for all of this is our SAM.gov New-Opportunity Monitor, which watches keyword searches on a schedule and reports only genuinely new notices. The rest of this article teaches you to do the same thing by hand, in case you would rather run your own setup.
Method 1: SAM.gov saved searches with email alerts (10 minutes, free)
This is the feature most people walk right past. SAM.gov has saved searches, and saved searches for Contract Opportunities have a Notify option that emails you when new matches appear. This is documented in the GSA Federal Service Desk knowledge base, and it is the only SAM.gov domain where the notify feature exists.
Here is how to set it up:
- Sign in to SAM.gov (you need an account for this; anonymous visitors can search but cannot save).
- Run a Contract Opportunities search for your keywords.
- Click the Actions button in the upper right, then Save. Give the search a name.
- Go to Saved Searches (from the search results page or your Workspace).
- Click the Actions menu next to your saved search and select Notify.
That is it. On any day your search has new results, SAM.gov sends an email to your registered address. An Army Corps of Engineers guidance document notes these notifications go out once daily at 9 p.m. Eastern.
Two honest caveats. First, the email is just a link to your saved search. It does not tell you what matched, how many notices matched, or whether any of them are worth your time. You click, you log in, you read. Second, the daily cadence means a solicitation with a short response window can burn most of its fuse before you see it.
When this is enough: you have one or two keyword searches, you check email every morning anyway, and your targets are mostly Solicitations and Combined Synopsis/Solicitations with normal response windows. For a lot of small firms, this genuinely covers it.
When it is not: you want routing control (Slack, a shared inbox, a webhook into your CRM), you want alerts faster than once a day, or you watch enough searches that the daily link click becomes its own chore. Keep reading.
Method 2: Poll the public search API with a script (free, real control)
SAM.gov's search page is powered by a public, unauthenticated search API. No API key, no login, no proxy. The endpoint looks like this:
https://sam.gov/api/prod/sgs/v1/search/
It takes query parameters. The ones that matter: index=opp (the Contract Opportunities index), q (your keyword), qMode=ALL (mandatory, without it the keyword is silently ignored), size (how many results, between 10 and 1000), and sort=-modifiedDate (newest first).
A quick warning before the code, because this is the kind of thing that bites people. The sort is by modified date, not publish date. A notice amended this morning sorts as "newest" even if it was originally published four months ago. So you cannot diff by position or by date. You must diff by notice ID. Dedup is the whole product here, same as it is in every monitor I have ever built.
Here is the whole thing in about 40 lines:
import json, os, urllib.parse, urllib.request
API = "https://sam.gov/api/prod/sgs/v1/search/"
SEEN_FILE = "samgov_seen_ids.json"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
def search(keyword, size=50):
params = {
"index": "opp",
"q": keyword,
"qMode": "ALL",
"page": "0",
"size": str(size),
"mode": "search",
"sort": "-modifiedDate",
"is_active": "true",
}
url = API + "?" + urllib.parse.urlencode(params)
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=45) as resp:
return json.load(resp)
def main():
seen = set(json.load(open(SEEN_FILE))) if os.path.exists(SEEN_FILE) else set()
data = search("cybersecurity")
results = data.get("_embedded", {}).get("results", [])
new_notices = []
for item in results:
notice_id = str(item.get("_id") or "")
if notice_id and notice_id not in seen:
new_notices.append({
"id": notice_id,
"title": item.get("title") or "",
"solicitation": item.get("solicitationNumber") or "",
"url": f"https://sam.gov/opp/{notice_id}/view",
})
for n in new_notices:
print(f"NEW: {n['title']} ({n['solicitation']})\n {n['url']}")
# send_to_slack(f"New SAM.gov opportunity: {n['title']}\n{n['url']}")
seen.update(str(item.get("_id") or "") for item in results)
json.dump(sorted(seen), open(SEEN_FILE, "w"))
print(f"Checked {len(results)} notices, {len(new_notices)} new.")
if __name__ == "__main__":
main()
Run it on a cron every hour or two. The samgov_seen_ids.json file is your entire database. Wire the send_to_slack line to Slack, Discord, email, or an SMS gateway and you have real push alerts routed wherever your team actually looks.
A few things worth knowing before you scale this up:
Watch Sources Sought notices, not just Solicitations. SAM.gov notice types include Solicitation (o), Combined Synopsis/Solicitation (k), Presolicitation (p), Sources Sought (r), Special Notice (s), and Award Notice (a). Sources Sought notices are presolicitation market research. Responding early with a capability statement puts you on the agency's radar before the formal solicitation exists, and most of your competitors will never know the opportunity was brewing. Several govcon coaches specifically advise filtering for these.
Do not trust server side NAICS filtering on this API. In testing, NAICS and set aside parameters on the frontend search API silently fail open, meaning you get results outside your filter without any error. Keyword search is fully server side and reliable; notice type filtering is safest done client side after you fetch. And remember that contracting officers assign NAICS codes inconsistently. A well known example in govcon circles: an IT contract might land under 541512 one week and 541519 the next. Search adjacent codes or just use broad keywords.
The script's real fragility is operational, not technical. One search is easy. Ten searches means ten state files, ten cron entries, and ten chances for a crash between "detect new notice" and "save state" to produce either duplicate alerts or a missed one. Move machines and the state files do not come with you. I built method 3 because I got tired of babysitting exactly this.
Method 3: A scheduled monitor that handles the boring parts (no infrastructure)
This is the version I run now: a small Apify actor that watches SAM.gov keyword searches on a schedule and reports only genuinely new notices. You configure up to 20 searches per run, each with a keyword, an active only toggle, a result cap, and optional notice type filters. On each run it fetches the newest results, diffs by notice ID against a persisted snapshot, and outputs only notices it has never seen before. Amendments to notices you have already seen are not reported again.
The output is a structured dataset per new notice: title, solicitation number, notice type, agency, office, publish date, response due date, and a direct sam.gov/opp/<id>/view link. New results land in an Apify dataset, which you can wire to Slack, email, Google Sheets, or webhooks through Apify's integrations. A scheduled check that finds nothing new outputs an empty dataset and costs almost nothing.
The SAM.gov New-Opportunity Monitor is priced per new opportunity: $0.001 per run start plus $0.0025 per genuinely new notice reported. A quiet daily run costs a fraction of a cent. You pay when there is something worth your attention, not for every notice scanned.
I will not pretend it is the only way to do this. Method 2 is genuinely fine if you like running your own infrastructure, and the code above is yours to keep. The actor exists for people who would rather set a schedule once and get pinged, with no cron jobs, no state files, and no "the laptop was asleep" gaps.
Which method should you pick?
- One or two searches and you live in email? Method 1. Saved searches with Notify, ten minutes, free, done.
- Want push alerts and routing control and do not mind a little ops? Method 2. The script above, a cron job, somewhere always on.
- Want alerts with zero infrastructure and clean dedup? Method 3. Set it once, get pinged only when something is actually new.
FAQ
Do I need a SAM.gov account?
Only for method 1. Saved searches and the Notify feature require a login. Methods 2 and 3 use the public search API, which needs no key and no login.
How fast are the alerts?
Method 1 sends one email per day on days your saved search has new results. Method 2 runs at whatever interval your cron uses; hourly or every two hours is polite and more than fast enough. Method 3 runs on your Apify schedule, hourly or daily.
Will SAM.gov block the API polling?
At reasonable intervals, no. The frontend API powers SAM.gov's own search box, and a query every hour or two is nothing compared to normal site traffic. Do not poll every minute from multiple IPs; that is how you get rate limited anywhere.
Does this catch amendments to notices I already saw?
By design, no. The API sorts by modified date, so an amended notice looks "newest," but both the script and the actor diff by notice ID, which means each notice is reported exactly once. If you need amendment tracking, use SAM.gov's Follow feature on individual notices.
Should I watch Award Notices too?
Yes, as competitive intelligence. Award notices tell you who won what and at what price, which feeds your pricing strategy for the next round. They also reveal which agencies are actively buying what you sell.
Can I watch multiple keywords at once?
Method 1: one saved search per keyword set. Method 2: loop the search() function over a list of keywords and keep one state file per keyword (or namespace the IDs). Method 3: the input takes up to 20 searches in a single run.
If you would rather not babysit a script, the SAM.gov New-Opportunity Monitor is method 2 as an actor: up to 20 keyword searches on a schedule, routed to Slack or email, and you only pay when it finds something genuinely new. Set it once and stop worrying about the 9 p.m. email lottery!
Top comments (0)