DEV Community

Loomwise
Loomwise

Posted on

Daily SAM.gov Alerts in Your NAICS Codes (No API Key)

By Loomwise

If you sell to the federal government, the routine is the same every day: open SAM.gov, re-run your searches, and scan for anything new in your NAICS codes before the response window closes. This post covers three ways to automate that. Pick the one that matches how much you want to do with the data.

Approach Cost What you get Best when
A. SAM.gov saved search + Notify Free An email with a link to your search results You track one or two searches and just want a nudge
B. SAM.gov Get Opportunities API Free (API key needed) JSON per request You're a developer and stay within your daily request limit
C. Scheduled feed from SAM.gov's public daily extract Pay per result Rows in email, a Drive file/Sheet or Slack You want the actual notices, filtered, in your own tools

Before you start: list your NAICS codes

Write down the 6-digit NAICS codes you bid under. Your SAM.gov entity registration is a good starting point, and the official lookup is at census.gov/naics. Think about prefixes too: 5415 covers every 5415xx code (computer systems design and related services). If agencies tend to misclassify your kind of work, add a few keywords such as "janitorial" or "HVAC".

Option A: SAM.gov saved search with Notify (free)

This is built into SAM.gov, and a lot of people miss it.

  1. Sign in to SAM.gov. You need a free account to save searches.
  2. Search the Contract Opportunities domain. Set NAICS, set-aside, place of performance and notice type in the left-hand filters.
  3. Click Actions → Save and name the search.
  4. Open Saved Searches, click Actions next to the search, and choose Notify. Saving a search on its own sends no email.

The limit: the email points you to your search results and doesn't list which notices matched. You still click through and read every time.

Option B: the official API

GSA's Get Opportunities Public API (https://api.sam.gov/opportunities/v2/search) takes ncode (one NAICS code), ptype, typeOfSetAside, state, and a required postedFrom/postedTo range in MM/dd/yyyy format. It needs an API key from your SAM.gov account. GSA's user guide sets the default limit for a non-federal individual who isn't associated with an entity at 10 requests per 24 hours. Associated users and system accounts get more. Because each request takes one NAICS code, tracking a dozen codes can use up a small quota quickly. Docs: open.gsa.gov/api/get-opportunities-public-api.

Option C: a scheduled feed from the public daily extract (no API key)

SAM.gov also publishes a public daily data extract of all active opportunities in its Data Services section. Anyone can download it without an API key. The file is large, so the useful part is filtering it down to your codes, scheduling the filter, and sending the results somewhere you'll see them. The steps below use a ready-made Apify Actor that does this (linked at the end). The same pattern works for any scheduled Apify run.

Step 1: Build your input

This input returns open solicitations from the last two days in two facilities-services NAICS codes, small-business set-asides only, performed in Virginia, Maryland or DC:

{
  "naicsCodes": ["561720", "561730"],
  "setAsideTypes": ["SBA", "SDVOSBC", "WOSB"],
  "placeOfPerformanceStates": ["VA", "MD", "DC"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Sources Sought"],
  "postedWithinDays": 2,
  "excludeExpired": true,
  "dedupeBySolicitation": true,
  "maxResults": 300,
  "includeSam": true,
  "includeGrants": false
}
Enter fullscreen mode Exit fullscreen mode

What each field does:

  • naicsCodes takes exact codes or prefixes (5415 matches every 5415xx code).
  • keywords (not used above) matches any keyword in the title, solicitation number or full description, ignoring case. Use it with NAICS codes or instead of them.
  • setAsideTypes uses SAM.gov's codes. For example SBA (total small business), 8A, HZC (HUBZone), SDVOSBC, WOSB, EDWOSB, or NONE for unrestricted notices.
  • placeOfPerformanceStates takes two-letter codes. When this filter is set, notices without a listed place of performance are excluded. Leave it empty if you also want those.
  • noticeTypes is worth widening. Sources Sought and Presolicitation notices give you a heads-up before the solicitation drops.
  • postedWithinDays counts days including today, in UTC. 0 means no limit. You can use postedFrom/postedTo instead for a fixed range.
  • excludeExpired (default true) drops notices whose response deadline has passed. Notices with no deadline are kept.
  • dedupeBySolicitation keeps only the newest notice per solicitation number. SAM.gov posts amendments and follow-ups as separate records.
  • maxResults caps the total rows. You pay per result, so this also caps cost.

Step 2: Run it once and check a row

The first run is the test. Each result looks like this (the fundingInstrument, assistanceListings, awardCeiling and awardFloor fields are null for contracts and only filled in for grants):

{
  "id": "6196197ae63848ce93a86870cb18e0cd",
  "source": "sam.gov",
  "title": "Helena - Lewis and Clark National Forest Dry Pole Pit Rock Crushing Service",
  "solicitationNumber": "1240BE27Q0001",
  "agency": "AGRICULTURE, DEPARTMENT OF",
  "subAgency": "FOREST SERVICE",
  "office": "WALLOWA WHITMAN NATIONAL FOREST",
  "noticeType": "Combined Synopsis/Solicitation",
  "naicsCode": "237310",
  "classificationCode": "Z1LB",
  "setAsideCode": "SBA",
  "setAside": "Small Business Set Aside - Total",
  "postedDate": "2026-10-17",
  "responseDeadline": "2026-10-22T13:00:00-06:00",
  "archiveDate": "2026-11-06",
  "popCity": "Stanford",
  "popState": "MT",
  "popZip": null,
  "popCountry": "USA",
  "link": "https://sam.gov/workspace/contract/opp/6196197ae63848ce93a86870cb18e0cd/view",
  "descriptionSnippet": "Amendment 0001 - This amendment provides missing solicitation attachments, issues Government responses to offeror questions…",
  "fundingInstrument": null,
  "assistanceListings": null,
  "awardCeiling": null,
  "awardFloor": null
}
Enter fullscreen mode Exit fullscreen mode

Contact names, emails and phone numbers are left out. Open link when you're ready to read the full notice and respond. Each run also writes a RUN_SUMMARY record with the source file's timestamp, so you can check how fresh the data is.

Step 3: Save it as a task

In Apify Console, click Save as a new task on the Actor page, give it a name (for example daily-facilities-va-md-dc), and paste the input above. Tasks are reusable saved configurations. Consultants can keep one task per client profile.

Step 4: Schedule it daily

SAM.gov regenerates the extract once a day, overnight US time, so a notice posted today usually appears the next morning. Schedule for the morning:

  1. Schedules → Create new.
  2. Set the cron expression 0 8 * * * (08:00 every day) and choose your timezone, for example America/New_York. Schedules handle daylight-saving changes.
  3. Add → Actor task and choose your task.
  4. Enable the schedule. New schedules start disabled.

Why postedWithinDays: 2 rather than 1? The window counts in UTC and the extract refreshes overnight, so a two-day window with de-duplication on id in your destination (Step 6) won't miss notices posted near midnight. The trade-off is that you pay again for repeat rows.

Step 5: Send results where you'll see them

Open the task's Integrations tab:

  • Email: add Send results email via Gmail, connect a Google account, set the subject and body, and attach the dataset as CSV or Excel. By default it fires only on successful runs.
  • Google Drive / Sheets: add Upload results to GDrive and pick the file name and format. Each successful run uploads a file to the Apify Uploads folder, and a CSV or XLSX file there opens in Google Sheets. To append rows to one running sheet instead, use Make or Zapier: an Apify "Watch Actor Runs" trigger, then "Get Dataset Items", then the Google Sheets "Add Rows" step. Apify's Make docs walk through exactly this.
  • Slack: add the Slack integration, connect your workspace, pick a channel and the "run succeeded" event. The message field accepts a Handlebars template, so you can post a link to the run's dataset, e.g. <https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}|Today's opportunities>. If a run fails, a second "run failed" message tells you to look.
  • Anything else: webhooks and the REST API let you send rows to a CRM, a bid board or a newsletter tool.

Step 6: Keep only what's new and keep costs predictable

  • De-duplicate on id in your destination (a "remove duplicates" step in Sheets, or an upsert in a database). Overlapping windows will then never show the same notice twice.
  • Leave dedupeBySolicitation on if you only want the latest version of each solicitation. Turn it off if you want to see every amendment.
  • Set maxResults and the run's maximum-charge option so one broad filter can't produce a surprise bill.

Tips that matter more than the tooling

  • Watch Sources Sought. They show what an agency plans to buy, and responding can shape the set-aside decision.
  • Use NAICS prefixes plus a few keywords. Contracting officers pick NAICS codes inconsistently, and a keyword match on the full description catches what the codes miss.
  • Read the deadline, not the posted date. responseDeadline carries its timezone offset, so sort by it.
  • Grants are separate. If you also pursue grants, setting includeGrants: true adds Grants.gov opportunities in the same schema. NAICS, set-aside, state and notice-type filters don't apply to grants, because Grants.gov doesn't publish those fields.

Disclosure: we built the Actor used in Option C, the SAM.gov Contract Opportunities Scraper & Feed. It costs $2 per 1,000 results, plus Apify's small Actor-start fee. Options A and B work fine on their own if you don't need it.

Top comments (0)