# SAM.gov Contract Opportunities & New-Notice Alerts (`stego-bot/samgov-opportunities`) Actor

Search and monitor US federal contract opportunities from SAM.gov's official public data extract. Filter by keyword, NAICS, PSC, set-aside, agency, notice type, posted date and state. Get only new notices since your last run. No login, no API key.

- **URL**: https://apify.com/stego-bot/samgov-opportunities.md
- **Developed by:** [Stego](https://apify.com/stego-bot) (community)
- **Categories:** Business, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 opportunity returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SAM.gov Contract Opportunities & New-Notice Alerts

Find US federal contract opportunities from SAM.gov by keyword, NAICS, PSC, set-aside, agency, notice type, state and posted date. Get clean, flat records you can download as JSON, CSV or Excel. You can also run it on a schedule and get **only the notices that are new since your last run**.

No SAM.gov account or API key is needed. The Actor reads SAM.gov's **official public Contract Opportunities data extract**, the bulk file the government publishes for reuse. It does not scrape the SAM.gov website.

### What it does

- Downloads and streams the official SAM.gov Contract Opportunities extract and filters it on the fly. The extract has about 70,000 rows, one per notice version; after combining amendments and dropping archived notices, that's roughly 35,000 unique active solicitations.
- Returns one flat row per notice: notice ID, title, solicitation number, agency hierarchy, notice type, set-aside, NAICS, PSC, posted and response dates, place of performance, award details, a direct SAM.gov link and, optionally, the official government points of contact.
- Keeps only the latest version of each solicitation by default, so you don't get five rows for one solicitation and its amendments.
- **Monitoring mode** remembers what it has already returned (in a named key-value store), so a daily schedule gives you only new or updated notices. The first run builds a baseline and returns nothing, and matches that don't fit in `maxResults` are kept for the next run instead of being dropped. That makes it easy to feed email, Slack, a CRM or Google Sheets through Apify integrations.
- If SAM.gov hasn't published a new extract since your last monitoring run, the Actor exits in seconds without downloading anything.

### Who it is for

- **Small businesses and GovCon BD teams:** a daily list of new solicitations in your NAICS codes and set-aside categories (8(a), SDVOSB, WOSB, HUBZone and others).
- **Capture managers and proposal writers:** track specific agencies, offices or keywords and see sources-sought and presolicitation notices early.
- **Consultants and bid-matching services:** pull filtered opportunity feeds for several clients, each with its own state key.
- **Analysts and researchers:** export notices, award notices and award amounts for market research.

### How to use

1. Open the Actor's input, then pick your filters: keywords, NAICS codes, set-asides, agencies, states and notice types.
2. Click **Start**. Results appear in the **Output** tab, and you can download them as JSON, CSV or Excel.
3. For alerts, turn on **Only new since last run**, give the search a **State key**, and add a daily schedule. The first run only remembers the current notices; each later run returns what's new.
4. Optionally connect an integration (email, Slack, Google Sheets, Zapier, Make, webhook) to receive each day's results automatically.

### Input

All inputs are optional. Called through the API with an empty input, the Actor returns the 100 most recent notices posted in the last 30 days. The Console form pre-fills an example keyword.

| Field | Description |
|---|---|
| `keywords` | Words or phrases to look for (case-insensitive). |
| `keywordMode` | `any` (default) or `all` keywords must appear. |
| `searchIn` | `titleAndDescription` (default) or `title` only (title and solicitation number). |
| `excludeKeywords` | Drop notices containing any of these. |
| `naicsCodes` | NAICS codes or prefixes, e.g. `541512` or `5415` (2–6 digits). |
| `pscCodes` | Product/Service Codes or prefixes, e.g. `D3`, `R425`. |
| `setAsides` | Set-aside codes: SBA, SBP, 8A, 8AN, HZC, HZS, SDVOSBC, SDVOSBS, WOSB, WOSBSS, EDWOSB, EDWOSBSS, VSA, VSS, ISBEE, IEE, BICiv, LAS, ESB, or NONE for unrestricted (NONE also matches notices with a blank set-aside, which is how most unrestricted notices are published). |
| `agencies` | Part of a department, sub-tier or office name (e.g. `Veterans Affairs`, `Navy`), or a CGAC/FPDS/AAC code. |
| `noticeTypes` | Solicitation, Combined Synopsis/Solicitation, Presolicitation, Sources Sought, Special Notice, Award Notice, Justification, Intent to Bundle, Sale of Surplus Property, Modification/Amendment/Cancel. |
| `states` | Two-letter place-of-performance state codes, e.g. `VA`, `MD`, `DC`. |
| `stateFallbackToOffice` | Many notices leave place of performance blank. When on (default), the contracting office's state is used instead. |
| `postedWithinDays` | Only notices posted in the last N days (default 30; 0 = everything in the extract). Ignored when `postedFrom` is set. |
| `postedFrom` / `postedTo` | Exact posted-date range (YYYY-MM-DD). |
| `onlyOpen` | Skip notices whose response deadline has passed. A date-only deadline counts as open through the end of that day (US Eastern). |
| `excludeArchived` | Skip notices whose archive date has passed (default on). |
| `onlyNew` | Monitoring mode: return only notices not returned by a previous run with the same state key. |
| `includeUpdates` | In monitoring mode, also return new amendments of solicitations you've already seen (marked `changeType: "updated"`). Default on. |
| `baselineOnFirstRun` | Default **on**. In monitoring mode, the first run (or the first run after you change the filters) only remembers the current notices and returns nothing, so you pay only the run start. Turn it off to receive, and pay for, the current backlog on the first run. |
| `stateKey` | Name for this monitor's memory (e.g. `client-a-cyber`). Leave empty to derive one from your filters. Use one key per saved search. |
| `stateStoreName` | Named key-value store that holds monitor memory. Default `samgov-opportunities-state`. |
| `maxResults` | Maximum notices to return per run, 1–100,000 (default 100). In monitoring mode, extra matches are returned by the next run. |
| `latestVersionOnly` | Return only the newest version of each solicitation (default on). Versions are grouped by solicitation number, so if a solicitation has since been followed by an award or special notice, only that newest notice is considered. |
| `includeContacts` | Default **off**. Include the official government points of contact (name, email, phone) published on the notice. This is personal data; see below. |
| `descriptionMaxLength` | Truncate the description to this many characters (default 1000; 0 = omit). |

#### Example inputs

Open cybersecurity opportunities for IT services firms:

```json
{
  "keywords": ["cybersecurity", "zero trust"],
  "naicsCodes": ["5415"],
  "postedWithinDays": 14,
  "onlyOpen": true,
  "maxResults": 50
}
```

SDVOSB construction work in the Mid-Atlantic:

```json
{
  "setAsides": ["SDVOSBC", "SDVOSBS"],
  "naicsCodes": ["2362", "2382"],
  "states": ["VA", "MD", "DC", "PA", "NC"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Presolicitation"]
}
```

A daily VA alert (schedule this once a day; the first run builds the baseline):

```json
{
  "agencies": ["Veterans Affairs"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Presolicitation", "Sources Sought"],
  "onlyNew": true,
  "stateKey": "va-alerts"
}
```

### Output

Each dataset item is one notice. Example with `includeContacts: true` (contact details replaced with placeholders):

```json
{
  "noticeId": "56ec6168eb9f4624a42acc9bc047233a",
  "title": "Cyber Security Engineering and Risk Management Framework Support",
  "solicitationNumber": "W91CRB-26-B-A006",
  "noticeType": "Presolicitation",
  "department": "DEPT OF DEFENSE",
  "subTier": "DEPT OF THE ARMY",
  "office": "W6QK ACC-APG",
  "postedDate": "2026-10-20T14:21:41-04:00",
  "responseDeadline": "2026-10-22T17:00:00-04:00",
  "setAsideCode": "8A",
  "setAside": "8(a) Set-Aside (FAR 19.8)",
  "naicsCode": "541519",
  "pscCode": "DJ01",
  "popCity": "Aberdeen Proving Ground",
  "popState": "MD",
  "popZip": "21005",
  "popCountry": "USA",
  "awardAmount": null,
  "awardee": null,
  "primaryContactName": "Contracting Officer name as published",
  "primaryContactEmail": "first.last.civ@army.mil",
  "primaryContactPhone": "520-000-0000",
  "url": "https://sam.gov/opp/56ec6168eb9f4624a42acc9bc047233a/view",
  "description": "The is a DRAFT solicitation to gain insight and questions on the requirement…",
  "extractLastModified": "Wed, 21 Oct 2026 03:30:45 GMT"
}
```

Other fields include `baseNoticeType`, `cgac`, `fpdsCode`, `aacCode`, `archiveDate`, `archiveType`, `popStreetAddress`, office city/state/zip/country, `awardNumber`, `awardDate`, `awardeeRedacted`, secondary contact fields, `additionalInfoLink`, and `changeType` (`new` or `updated`, in monitoring mode). The run's `OUTPUT` record in the default key-value store summarises the run: rows read, matches, megabytes read, whether the extract was unchanged, and so on.

### Pricing

Pay per event, with no subscription:

| Event | Price |
|---|---|
| Actor start | $0.005 per run |
| Opportunity returned | $0.002 per notice ($2 per 1,000) |
| Deep scan | $0.05 per run, charged only when the date range you request starts more than 90 days ago (e.g. `postedWithinDays: 91` or more, `0` for everything, or an old `postedFrom`). Never charged on baseline runs, incremental monitoring runs, or catch-up runs that deliver pending results. |

A daily alert that returns 10 new notices costs about $0.025 a day. Baseline runs, and monitoring runs where SAM.gov hasn't published a new extract, cost only the start fee. Set a maximum cost per run in the Apify Console: the Actor reserves any deep-scan fee first, then stops returning results before it goes over.

### How monitoring works

1. With `onlyNew` on, the Actor stores the IDs of notices it returned (and their solicitation groups) in the named key-value store under your `stateKey`.
2. **First run:** by default it records every current match as seen and returns nothing (a baseline), so alerts start with the next new notices.
3. Each later run skips anything already returned. A new amendment to a solicitation you've seen is returned as `changeType: "updated"` (turn off `includeUpdates` to skip those).
4. Each run normally reads only notices posted since the previous run, plus a 7-day safety margin, which keeps runs fast.
5. **Nothing is dropped:** if more notices match than `maxResults` (or your max charge) allows, the rest are marked pending and returned by the next run, even if SAM.gov hasn't published a new extract yet. The run log and status say so.
6. If the extract hasn't changed since the last run (same ETag) and nothing is pending, the run ends immediately.
7. **Changing filters:** the state remembers which filters it was built with. If you change them but keep the same `stateKey`, the next run starts over for the new filters (a fresh baseline by default). Notices you already received are still skipped. To keep separate alerts, use a separate `stateKey` for each.
8. Memory older than 400 days is pruned.

### Data source, freshness and limitations

- **Source:** the official SAM.gov "Contract Opportunities" public data extract (`ContractOpportunitiesFullCSV.csv`), which SAM.gov publishes through its Data Services for public reuse. SAM.gov's terms of use prohibit scraping the website; this Actor doesn't do that.
- **Freshness:** SAM.gov regenerates the extract **once a day** (around 03:30 UTC). Notices posted during the day show up in the next day's extract. This is not a real-time feed. For same-hour alerts, use SAM.gov's own saved searches.
- **Active notices only:** the extract covers currently active notices, not the full historical archive. The extract and the live website can disagree briefly on notices archived or posted since the last regeneration.
- **Place of performance** is often blank on notices. See `stateFallbackToOffice`.
- **Awardee names:** SAM.gov's terms restrict Dun & Bradstreet data in entity records last updated before 4 April 2022. As a precaution, the Actor withholds the awardee on award notices dated before that cutoff (`awardeeRedacted: true`). This is a conservative approximation, not a guarantee that no D\&B-sourced text remains.
- **Personal data:** with `includeContacts` on, the output contains names, work emails and phone numbers of government contracting staff, exactly as published on each notice. Contacts are off by default. If you turn them on, use them only to respond to the opportunity, not for marketing or bulk outreach, and handle them according to the data-protection rules that apply to you.
- If the extract's columns the Actor relies on go missing, or many rows have the wrong number of columns, the run fails with an error. Subtler format changes might not be detected, so check results against SAM.gov if something looks off.

### FAQ

**Do I need a SAM.gov account or API key?** No.

**Why didn't I get results for my state?** Many notices have no place of performance. Keep `stateFallbackToOffice` on, widen the date range, or drop some filters.

**Can I get attachments or full descriptions?** The extract includes the description text (truncated by `descriptionMaxLength`). Attachments are on the notice page; use the `url` field.

**Can I send alerts to email or Slack?** Yes. Schedule the Actor daily with `onlyNew` on, then add an Apify integration (email, Slack, Zapier, Make, Google Sheets, webhook) on the dataset.

### Disclaimer

This Actor is not affiliated with, endorsed by, or sponsored by the U.S. General Services Administration or SAM.gov. Data is provided as published by the government. Always confirm details, deadlines and amendments on SAM.gov before you bid.

# Actor input Schema

## `keywords` (type: `array`):

Words or phrases to look for (case-insensitive). Each entry is matched as a phrase. Leave empty to match everything.

## `keywordMode` (type: `string`):

Match notices containing ANY of the keywords, or only notices containing ALL of them.

## `searchIn` (type: `string`):

Where to look for keywords.

## `excludeKeywords` (type: `array`):

Drop notices whose title or description contains any of these phrases.

## `naicsCodes` (type: `array`):

NAICS codes or prefixes, e.g. 541512 or 5415 (matches every code starting with 5415).

## `pscCodes` (type: `array`):

Product Service Codes or prefixes, e.g. DA01 or D (all IT services).

## `setAsides` (type: `array`):

Only notices with one of these set-asides. Leave empty for all, including unrestricted.

## `agencies` (type: `array`):

Department, sub-tier agency or office names (partial match, e.g. 'Coast Guard', 'Veterans Affairs', 'NAVSEA'), or exact CGAC / FPDS / AAC codes (e.g. 070, 3600, N32253).

## `noticeTypes` (type: `array`):

Only these notice types. Leave empty for all.

## `states` (type: `array`):

Two-letter state or territory codes for the place of performance, e.g. VA, MD, DC.

## `stateFallbackToOffice` (type: `boolean`):

Many notices leave the place of performance empty. When on, such notices are matched on the contracting office's state instead.

## `postedWithinDays` (type: `integer`):

Only notices posted in the last N days. Used when 'Posted from' is empty. Set 0 to search the whole extract (all currently active notices).

## `postedFrom` (type: `string`):

Earliest posted date (YYYY-MM-DD). Overrides 'Posted within the last N days'.

## `postedTo` (type: `string`):

Latest posted date (YYYY-MM-DD), inclusive.

## `onlyOpen` (type: `boolean`):

Skip notices whose response deadline has passed. A date-only deadline counts as open through the end of that day (US Eastern). Notices without a deadline are kept.

## `excludeArchived` (type: `boolean`):

The daily extract can still contain notices whose archive date has passed. SAM.gov no longer shows these as active, so they are skipped by default.

## `onlyNew` (type: `boolean`):

Return only notices not returned by a previous run with the same state key. Ideal for daily scheduled alerts. By default the first run only builds a baseline and returns nothing.

## `includeUpdates` (type: `boolean`):

With 'Only new since last run', also return a solicitation again when a newer version (amendment) is posted. Such records get changeType = 'updated'.

## `baselineOnFirstRun` (type: `boolean`):

With 'Only new since last run', the first run for a state key (or the first run after you change the filters) records every currently matching notice as seen and returns nothing, so you are only charged the run start. Later runs return only new notices. Turn off to receive (and pay for) the current backlog on the first run.

## `stateKey` (type: `string`):

Name for the remembered state (letters, digits and - \_ . ! ' ( ) ; other characters become '-'). Leave empty to derive it automatically from your filters. Use a different key for each saved search. If you change the filters on an existing key, the next run starts over for the new filters (a fresh baseline when enabled); notices already returned are still skipped.

## `stateStoreName` (type: `string`):

Named key-value store that keeps seen notice IDs between runs.

## `maxResults` (type: `integer`):

Maximum number of notices to return per run (1-100000; you pay per returned notice). In monitoring mode, matches that do not fit are returned by the next run.

## `latestVersionOnly` (type: `boolean`):

The extract lists every amendment as a separate notice. Keep only the newest version of each solicitation (recommended).

## `includeContacts` (type: `boolean`):

Include the government points of contact (name, email, phone) exactly as published on the notice. This is personal data of government employees: use it only to respond to the opportunity, not for marketing or bulk outreach.

## `descriptionMaxLength` (type: `integer`):

Maximum characters of the notice description to include. 0 = omit the description.

## Actor input object example

```json
{
  "keywords": [
    "cybersecurity"
  ],
  "keywordMode": "any",
  "searchIn": "titleAndDescription",
  "stateFallbackToOffice": true,
  "postedWithinDays": 30,
  "onlyOpen": false,
  "excludeArchived": true,
  "onlyNew": false,
  "includeUpdates": true,
  "baselineOnFirstRun": true,
  "stateStoreName": "samgov-opportunities-state",
  "maxResults": 100,
  "latestVersionOnly": true,
  "includeContacts": false,
  "descriptionMaxLength": 1000
}
```

# Actor output Schema

## `opportunities` (type: `string`):

Dataset with one item per matching SAM.gov contract opportunity notice.

## `summary` (type: `string`):

OUTPUT record with result counts, extract freshness and monitoring status for this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "cybersecurity"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stego-bot/samgov-opportunities").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "keywords": ["cybersecurity"] }

# Run the Actor and wait for it to finish
run = client.actor("stego-bot/samgov-opportunities").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keywords": [
    "cybersecurity"
  ]
}' |
apify call stego-bot/samgov-opportunities --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stego-bot/samgov-opportunities"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ackgOaop8IqUJLV7T/builds/88pvDtEqzyUonG8Ym/openapi.json
