Guide · Public sector data

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

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:

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:

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.