跳到正文
DEV Community· FedSignals Data·· 3 小時前AI 評分35

如何在 Slack 接收 SAM.gov 每日合約機會提示(毋須 API 金鑰)

How to Get Daily SAM.gov Contract Opportunity Alerts in Slack (No API Key Needed)

AI 導讀

教學示範如何使用 Apify 的 Federal Contract Opportunities Monitor Actor,篩選 SAM.gov 新聯邦招標並推送至 Slack 或 webhook,毋須 SAM.gov 帳戶或 API 金鑰。

正文

Filter new federal bids by category, set-aside and NAICS, then push only new notices to Slack or a webhook each morning.

If you sell to the federal government, the job starts with SAM.gov. Every solicitation, sources-sought notice and presolicitation is posted there, and if you miss one, you can miss a response deadline that is only a week away.

Most small contractors use one of three approaches:

  1. Saved searches on SAM.gov. These work, but the email digests are hard to filter and hard to send anywhere other than your inbox.
  2. The SAM.gov Get Opportunities API. It is powerful, but it requires a personal API key, and GSA limits daily requests by account role. GSA's published rate-limit tables for SAM.gov APIs list 10 requests per day for a non-federal user with no role in SAM.gov (example table). Each request is also capped at 1,000 records.
  3. A paid GovCon platform. It's a good choice if you need pipeline management, but expensive if all you want is "tell me about new IT bids set aside for small business."

This tutorial shows a fourth option. You run a small, scheduled job that reads SAM.gov's public Contract Opportunities data extract, filters it, and sends only the new notices to Slack or any webhook. You don't need a SAM.gov account or API key.

We'll use the Federal Contract Opportunities Monitor Actor on Apify, which we maintain. You can run everything below from the Apify web console or with a few lines of Python.

What you'll build

  • A daily run at 7:00 AM Eastern that checks the last 3 days of SAM.gov postings.
  • Filters for your niche, for example IT & Cyber notices with a small business set-aside and at least 5 days left to respond.
  • Deduplication, so each notice reaches you only once.
  • A Slack message (or a webhook to your CRM) whenever something new shows up.

Step 1: Run the Actor once by hand

Open apify.com/fedsignals/federal-opportunities-monitor and click Try for free. A free Apify account is enough for testing.

The input form uses plain-English filters. Here is the exact input we used for this article:

{
  "daysBack": 3,
  "categories": ["IT & Cyber"],
  "setAsides": ["small business"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Sources Sought"],
  "minDaysToDeadline": 5,
  "onlyNew": false,
  "pushRunSummary": true,
  "maxItems": 5
}

What each setting does:

  • daysBack: 3 looks at notices posted in the last 3 days.
  • categories groups PSC and NAICS codes into 16 readable buckets such as "IT & Cyber", "Facilities" or "Professional Services". If you need exact codes, use naicsPrefixes or pscPrefixes instead.
  • setAsides accepts plain names like small business, 8(a), hubzone, sdvosb and wosb, or raw SAM.gov codes like SBA.
  • minDaysToDeadline: 5 drops anything you couldn't realistically respond to in time.
  • onlyNew: false is for this first test only. You'll turn it on in Step 3.
  • maxItems: 5 keeps the test small.

Step 2: Look at the output

Our run on Oct 29, 2026 scanned 555 notices posted from Oct 26 to Oct 29. It stopped at the 5-notice cap and finished in about 3 seconds. Here is one result, trimmed:

{
  "noticeId": "99374b795a4e4248bfc853d4b25b0cf9",
  "title": "SIPR LAN DROPS",
  "solicitationNumber": "FA282326QA054",
  "noticeType": "Combined Synopsis/Solicitation",
  "postedDate": "2026-10-28",
  "responseDeadline": "2026-11-04T11:00:00-06:00",
  "daysUntilDeadline": 6,
  "category": "IT & Cyber",
  "pscCode": "7G21",
  "naicsCode": "541519",
  "department": "DEPT OF DEFENSE",
  "subTier": "DEPT OF THE AIR FORCE",
  "setAside": "Small Business Set Aside - Total",
  "placeOfPerformance": { "state": "FL", "zip": "32542", "country": "USA" },
  "url": "https://sam.gov/opp/99374b795a4e4248bfc853d4b25b0cf9/view",
  "attachmentsPageUrl": "https://sam.gov/opp/99374b795a4e4248bfc853d4b25b0cf9/view#attachments-links"
}

The other four results in that run:

Title Agency Deadline (days left)
Pilot Training Transformation Device Support Services IV Air Force Nov 25 (27)
ACME SERVER Navy, NSWC Dahlgren Nov 10 (12)
LiDAR Data Collection Equipment Navy, NAVSUP FLC Norfolk Nov 4 (6)
LiDAR Data Collection Equipment Navy, NAVSUP FLC Norfolk Nov 4 (6)

The LiDAR solicitation shows up twice because SAM.gov published it as two separate notices (different noticeIds, same solicitationNumber). If you want one row per solicitation, group by solicitationNumber downstream.

Because pushRunSummary was on, the dataset also ends with one free summary row:

{
  "recordType": "run-summary",
  "scanned": 555,
  "pushed": 5,
  "skippedAlreadySeen": 0,
  "byCategory": { "IT & Cyber": 5 },
  "windowStart": "2026-10-26",
  "windowEnd": "2026-10-29"
}

That row is handy for alerts. You can write "5 new IT bids" without counting rows yourself.

Step 3: Call it from Python

Every Apify Actor has a REST API. This snippet runs the Actor synchronously and prints the new notices. It reads your Apify API token from an environment variable, so never paste the token into code. The maxTotalChargeUsd parameter is a hard spending cap per run.

import os
import requests

ACTOR = "fedsignals~federal-opportunities-monitor"
URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items"

run_input = {
    "daysBack": 3,
    "categories": ["IT & Cyber"],
    "setAsides": ["small business"],
    "minDaysToDeadline": 5,
    "onlyNew": True,            # only notices this stateKey hasn't returned before
    "stateKey": "it-smallbiz",  # one key per saved search
    "maxItems": 50,
}

resp = requests.post(
    URL,
    params={"maxTotalChargeUsd": 0.25},
    headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
    json=run_input,
    timeout=300,
)
resp.raise_for_status()

for n in resp.json():
    if n.get("recordType") == "run-summary":
        continue
    print(f"{n['responseDeadline'][:10]}  {n['title']}  {n['url']}")

Run it twice in a row. The second run returns nothing, because onlyNew remembers every notice ID it has already delivered for that stateKey. In our own dedup test, an immediate rerun returned 0 duplicates.

Step 4: Schedule it daily

The SAM.gov extract is refreshed once a day, overnight, so a morning run is enough. This is a daily feed, not a real-time one.

  1. In the Apify console, open the Actor and click Save as a new task. Put in your filters, set onlyNew: true and daysBack: 3, and give it a stateKey.
  2. Turn on baselineOnFirstRun. The first run then marks everything that currently matches as "seen", returns nothing and costs nothing, so you won't get a flood of old notices on day one.
  3. Go to Schedules → Create new, use the cron expression 0 7 * * *, set the timezone to America/New_York, and attach your task.

Keeping daysBack at 3 covers weekends and late postings, and onlyNew stops the overlap from producing duplicates.

Step 5: Send alerts to Slack or a webhook

You have two options.

Option A: Apify's built-in Slack integration. On your task, open Integrations, choose Slack, connect your workspace and pick a channel. This is the fastest way to get a message every time the run succeeds.

Option B: A webhook that lists each new bid. Add a webhook on the task for the event ACTOR.RUN.SUCCEEDED, pointing at a small function you host, such as a Cloud Function, AWS Lambda or a Cloudflare Worker. Apify's default webhook payload includes the run's resource.defaultDatasetId. The function fetches the items and posts them to a Slack incoming webhook:

import os
import requests

def handler(payload: dict) -> None:
    dataset_id = payload["resource"]["defaultDatasetId"]
    items = requests.get(
        f"https://api.apify.com/v2/datasets/{dataset_id}/items",
        headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
        timeout=60,
    ).json()

    notices = [i for i in items if i.get("recordType") != "run-summary"]
    if not notices:
        return  # nothing new today, stay quiet

    lines = [f"*{len(notices)} new SAM.gov opportunities*"]
    for n in notices[:20]:
        lines.append(
            f"• <{n['url']}|{n['title']}> ({n['subTier'] or n['department']}), "
            f"due {n['responseDeadline'][:10]}"
        )
    requests.post(os.environ["SLACK_WEBHOOK_URL"], json={"text": "\n".join(lines)}, timeout=30)

You can use the same webhook to send rows to a CRM, Google Sheets, Zapier or Make instead of Slack.

What it costs

The Actor uses pay-per-event pricing: $0.0025 per notice delivered, plus a $0.01 alert fee only on runs that find at least one new notice, plus Apify's $0.00005 run-start fee. The test run in this article (5 notices) works out to 5 × $0.0025 + $0.01 + $0.00005 ≈ $0.023. A 7-day IT & Cyber small-business search in our Oct 28 test returned 18 notices, about $0.055. A day with nothing new costs only the start fee. Pulling everything from all agencies is much larger: our 3-day all-agency test returned 1,884 notices, about $4.72. That's why filters and maxItems matter.

FAQ

Do I need a SAM.gov account or API key?
No. The Actor reads SAM.gov's public Contract Opportunities extract. A key is optional and only adds direct attachment download links. Without one, every notice still includes attachmentsPageUrl.

Is this scraping SAM.gov?
No. It uses the official public data extract that SAM.gov publishes for download, not the search pages. Check SAM.gov's terms for your own use case.

How fresh is the data?
The extract updates once a day, overnight. Notices posted today usually appear in the next morning's run.

Can I track award notices or one specific solicitation?
Yes. Set noticeTypes to Award Notice to see awardees and amounts. For one solicitation, put its number in solicitationNumbers with a longer daysBack (for example 90) to get the original notice, its amendments and the award.

Can I run several searches?
Yes. Save one task per search, each with its own stateKey, so their "already seen" lists stay separate.

Does it work with AI agents?
Yes. Apify Actors can be added as tools through the Apify MCP server, so an assistant can run the same search on request.


FedSignals Data builds small public-data monitors on Apify. FedSignals is not affiliated with, or endorsed by, GSA or SAM.gov. Questions or feature requests: fedsignals.alerts@gmail.com.

來源:DEV Community · dev.to