SAM.gov amendment alerts: how to catch changes to solicitations you're bidding on
A saved search helps you find notices. The changes that hurt a bid usually happen to a notice you've already found: a moved response deadline, a new amendment, a changed notice type. Here's how to watch for them, either yourself or with a tool.
What you're up against
- Track the solicitation number, not the notice link. In SAM.gov's public extract, an amendment or follow-on notice appears as a new notice ID under the same solicitation number (
Sol#). An old bookmarked link can look unchanged. - The API shows only the latest version. GSA's Get Opportunities API "only provides the latest active version of the opportunity". So keep your own snapshot and compare.
- The data refreshes daily. The public CSV extract is regenerated once a day, so anything built on it gives next-day alerts, not real-time ones.
- Attachments aren't in the extract. For anything you're pricing, open the attachments on SAM.gov before you submit.
A daily diff in Python
This reads the public extract (no API key needed), keeps every row for the solicitation numbers you're watching, and compares notice IDs, type, deadline and a description hash with yesterday's snapshot.
import csv, hashlib, io, json, pathlib, requests
EXTRACT = ("https://sam.gov/api/prod/fileextractservices/v1/api/download/"
"Contract%20Opportunities/datagov/ContractOpportunitiesFullCSV.csv?privacy=Public")
WATCH = {"YOUR-SOLICITATION-1", "YOUR-SOLICITATION-2"} # Sol# values you are pursuing
def snapshot():
out = {}
with requests.get(EXTRACT, stream=True, timeout=60) as r:
r.raise_for_status()
r.raw.decode_content = True
rows = csv.DictReader(io.TextIOWrapper(r.raw, encoding="cp1252", newline=""))
for row in rows: # whole file: amended notices can be old
sol = (row.get("Sol#") or "").strip()
if sol in WATCH:
out.setdefault(sol, []).append({
"notice": row["NoticeId"], "type": row["Type"],
"deadline": row["ResponseDeadLine"],
"desc": hashlib.sha256((row.get("Description") or "").encode()).hexdigest()[:12],
})
return out
state = pathlib.Path("sam_state.json")
old = json.loads(state.read_text()) if state.exists() else {}
new = snapshot()
for sol in WATCH:
before = sorted(old.get(sol, []), key=json.dumps)
after = sorted(new.get(sol, []), key=json.dumps)
if before != after:
print(f"CHANGED {sol}: {before} -> {after}") # new version, deadline, type, text, or gone
state.write_text(json.dumps(new))
The first run records a baseline. A watched number that disappears is reported too; check it on SAM.gov. The file is large and encoded as Windows-1252, and streaming keeps memory flat. If you'd rather use the keyed API, see SAM.gov API: how it works.
We built this
Disclosure: Your Evenings Ltd built and sells this. It isn't an official government product and isn't affiliated with GSA or SAM.gov.
SAM.gov & Grants.gov Scraper on the Apify Store has solicitation tracking. List the numbers you're bidding on (up to 500; case, spaces and punctuation are ignored) and schedule a daily run:
{
"trackSolicitationNumbers": ["YOUR-SOLICITATION-1", "YOUR-SOLICITATION-2"],
"stateStoreName": "my-bids-watchlist"
}
Each run reads SAM.gov's whole public extract and compares the active notices under your numbers with what it saved last time. You get one change row for each:
new_notice: a new notice or amendment under the number, with the fields that differ from the previous notice (deadline, notice type, set-aside, NAICS, title, description text and others), before and after.notice_updated: a notice already seen was edited in place.notice_removed: a notice left the active extract. The extract doesn't say whether it was archived, cancelled or taken down, so check SAM.gov.
Each change row has a plain-English changeSummary. Status rows are free: tracking_started (first run), not_found (number not in the active extract yet), unchanged (so every number appears in every run) and changes_pending (changes held back because a run hit your maximum charge, returned next run).
Price: $0.003 per change row, plus $0.01 per run that finds at least one change. A run with no changes costs only the Actor start. Current prices are on the Pricing tab.
Caveats:
- Next-day, not real-time. SAM.gov refreshes the extract about once a day, so schedule the run after 04:00 UTC.
- Attachment-only changes aren't seen unless SAM.gov also posts a new notice or edits the notice's fields.
- Every row has
source_updated_at, the extract's Last-Modified time.
FAQ
Can I get real-time SAM.gov amendment alerts? Not from the public daily extract. It's regenerated once a day, so alerts built on it are next-day.
How do I get alerts when a solicitation is amended?
Track it by solicitation number and compare each day's notices with the last snapshot, either with the script above or with the Actor's trackSolicitationNumbers.
What does a day with no changes cost on the Actor? Only the Actor start. Status rows are free.
Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them. This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.