# SAM.gov Opportunity Monitor (`sellavir/sam-gov-opportunity-monitor`) Actor

Monitors SAM.gov federal contract opportunities by NAICS, keyword, notice type and set-aside. Delta mode emits only new/modified notices (incl. deadline changes and cancellations) since your last run.

- **URL**: https://apify.com/sellavir/sam-gov-opportunity-monitor.md
- **Developed by:** [Vic](https://apify.com/sellavir) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SAM.gov Opportunity Monitor

Monitors [SAM.gov](https://sam.gov) — the U.S. government's single publication point for federal contract opportunities — by NAICS code, keyword, notice type and set-aside. **Delta mode** (the default) remembers what it has already shown you and emits only notices that are **new or changed since your last run**, including deadline moves and cancellations. Award notices are covered too, so you can watch who won.

### Quickstart

1. Set `naicsCodes` (e.g. `["541511"]`) and/or `keywords`, keep `deltaMode: true`.
2. Run once to seed the baseline (first run emits everything matching as `"new"`).
3. Schedule it daily — every later run emits only what changed, with `deltaType`, `prevModifiedDate` and `deadlineChanged`.

### Why delta mode

Capture/BD teams do not need 600 unchanged rows every morning; they need the 12 that are new, amended, extended, or canceled. Delta state is kept per filter-combination in a named key-value store (`sam-gov-monitor-state`) in **your** Apify account, so runs are small, fast and cheap. Turn `deltaMode` off to get a full snapshot instead.

### What it does NOT do

- No login-gated data, no entity registrations or exclusions, no bid submission.
- No consumer PII. Contracting-officer point-of-contact fields (official business contacts published by the government) are **off by default**; set `includePointOfContact: true` to include them.
- `naicsDescription` is currently always `null` — the SAM.gov search/detail responses don't carry it. The field is kept for forward compatibility.

### Input example

```json
{
  "naicsCodes": ["541511"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Award Notice"],
  "modifiedSinceDays": 2,
  "deltaMode": true,
  "maxResults": 1000
}
```

Recipes:

- **Daily NAICS watch**: `naicsCodes: ["541511"]`, `deltaMode: true`, schedule daily.
- **Keyword watch**: `keywords: ["zero trust", "SIEM"]` — each keyword is queried separately and results are deduplicated before you are charged.
- **Award-notice feed**: `noticeTypes: ["Award Notice"]`, `activeOnly: false`, `modifiedSinceDays: 1`.
- **Set-aside filter**: `setAsideCodes: ["SBA", "SDVOSBC", "WOSB", "8A"]`.

Notice types accept either full names (`Solicitation`, `Presolicitation`, `Award Notice`, `Sources Sought`, `Special Notice`, `Combined Synopsis/Solicitation`, `Justification`, `Sale of Surplus Property`) or SAM one-letter codes (`o`, `p`, `a`, `r`, `s`, `k`, `u`, `g`, `i`).

### Output example (real row from a test run, 2026-08-29)

```json
{
  "noticeId": "ff5d8bbd2fdf4b6490b25bfb23e18a67",
  "solicitationNumber": "HC101926R0005",
  "title": "Virtual Reality Training System for Gas Turbine Generator - Pearl Harbor, HI",
  "noticeType": "Solicitation",
  "noticeTypeCode": "o",
  "department": "DEPT OF DEFENSE",
  "subTier": "DEFENSE INFORMATION SYSTEMS AGENCY (DISA)",
  "office": "DITCO-PAC",
  "naicsCode": "541511",
  "naicsDescription": null,
  "pscCode": "DA01",
  "setAside": "SDVOSBC",
  "placeOfPerformance": { "city": "JBPHH", "state": "HI", "zip": "96860", "country": "USA" },
  "postedDate": "2026-08-29T00:48:37.000Z",
  "modifiedDate": "2026-08-29T00:48:37.000Z",
  "responseDeadline": "2026-09-10T19:00:00.000Z",
  "isActive": true,
  "isCanceled": false,
  "url": "https://sam.gov/opp/ff5d8bbd2fdf4b6490b25bfb23e18a67/view",
  "descriptionSnippet": "The Defense Information Systems Agency - Pacific (DISA - PAC) is seeking a qualified contractor to develop a Virtual Reality Training System for a Gas Turbine Generator ... (truncated here; the actor emits up to 2,000 characters)",
  "award": null,
  "enriched": true,
  "deltaType": "new",
  "prevModifiedDate": null,
  "deadlineChanged": false,
  "scrapedAt": "2026-08-29T14:29:31.011Z"
}
```

Award notices additionally carry `award: { "awardeeName": "...", "awardeeUeiSAM": "..." }` when SAM.gov publishes it.

By default each emitted row is enriched with one extra detail request to fill `naicsCode`, `pscCode`, `setAside`, `placeOfPerformance` and the full description snippet (SAM.gov's list API does not include those). Set `enrichDetails: false` for faster, lighter rows with those fields `null`.

### Pricing (pay-per-event)

| Event | Price | When |
|---|---|---|
| Run start | $0.005 | once per run |
| Opportunity row | $0.003 | per row, when `deltaMode` is **off** |
| Delta row | $0.004 | per row, when `deltaMode` is **on** (charged **instead of**, never on top of, the opportunity-row price) |

You are only charged for rows actually written to the dataset — duplicates are removed first, unchanged notices in delta mode cost nothing, and an empty day costs just the run start. Worked examples: a full daily pull of ~500 notices ≈ $45/month; a delta watch that surfaces ~100 changes/day ≈ $12/month.

### How long does a run take / recommended timeout

Measured on the Apify platform (4 GB, default build). The cost driver is per-notice
detail enrichment (`enrichDetails: true`, ~1 request/s to stay polite):

| run shape | duration (measured 2026-09-03) | recommended timeout |
|---|---|---|
| Delta run, few changes (typical nightly) | ~30-60 s | default 3600s |
| ~150 notices with `enrichDetails: true` | ~2 min | default 3600s |
| ~1000 notices with `enrichDetails: true` | ~15-20 min | 3600s |
| Same but `enrichDetails: false` | ~2-3 min | default 3600s |

Bound time and cost with `maxResults`, tighter `naicsCodes`/`keywords`, or
`modifiedSinceDays`. `enrichDetails: false` is 5-10x faster if you only need
list-level fields.

### Timeouts, partial runs, and resurrection (no double billing)

The Actor watches its own run timeout and **stops gracefully ~20-30s before the
platform would kill it**: it flushes a delivery checkpoint into the run's key-value
store, finishes as SUCCEEDED, and sets a status message like
`Partial: N of ~M rows delivered...`.

- **Resurrect the same run** (Runs > resurrect, pick a longer timeout) and it
  **continues where it left off** - already-delivered rows are never re-pushed and
  never re-charged. The flat run-start fee is charged at most once per run.
- Rows are charged strictly **after** they are written to the dataset:
  a row that never reached your dataset is never billed.
- In **delta mode**, even a fresh follow-up run continues cleanly: delivered
  notices are recorded in your delta state, so they are not billed again unless
  they change.

### Data source & respect

Data comes from SAM.gov's public search API — U.S.-government public-domain data, readable without login or API key. The actor is deliberately polite: sequential requests with a fixed delay (under 2 requests/second), a descriptive User-Agent, and exponential backoff on 429/5xx responses. The endpoints used are not disallowed by SAM.gov's robots.txt. POC fields are official business contacts published by the government and are opt-in.

### Limitations

- SAM.gov caps any single query's pagination at 10,000 records; the actor scans the most-recently-modified records first and auto-slices large date windows per day, logging coverage warnings when a query exceeds the cap. Use `modifiedSinceDays` and filters to stay well under it.
- The primary endpoint is SAM.gov's internal site API and can change without notice. Every run starts with a contract test; on drift or hardening the actor falls back to the official Get Opportunities API **if** you supply a free [api.data.gov](https://api.data.gov) key in `apiDataGovKey` (the fallback has coarser filtering — posted-date windows, no modified-date filter — and lower daily quotas). Report breakage in the Issues tab; the goal is a response within 24 hours and a fix well inside 14 days.
- `modifiedSinceDays` windows are anchored to US-Eastern dates (as SAM.gov's API expects); all output timestamps are normalized to UTC ISO 8601.

# Actor input Schema

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

Full-text search terms (each queried separately, results deduped).

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

e.g. 541511. Combined with keywords as filters.

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

e.g. Solicitation, Presolicitation, Award Notice, Sources Sought, Special Notice, Combined Synopsis/Solicitation, Justification, Sale of Surplus Property, Intent to Bundle. Single-letter SAM codes (o, p, a, r, s, k, u, g, i) also accepted. Empty = all types.

## `setAsideCodes` (type: `array`):

SAM set-aside codes, e.g. SBA, SBP, 8A, 8AN, WOSB, EDWOSB, SDVOSBC, HZC. Empty = all.

## `activeOnly` (type: `boolean`):

Only notices currently open/active. Turn off to include archived/inactive notices.

## `modifiedSinceDays` (type: `integer`):

0 = no date filter. Keeps scheduled runs small and under the 10,000-record API cap.

## `deltaMode` (type: `boolean`):

Persists seen notice IDs + modified dates in a named key-value store; emits only new or modified notices, billed as delta-result events.

## `includePointOfContact` (type: `boolean`):

Official business contacts only (name/title/email/phone of the contracting office POC); OFF by default for privacy-conservative output.

## `enrichDetails` (type: `boolean`):

SAM.gov's search list omits NAICS/PSC/set-aside/place-of-performance; the actor fetches one extra detail request per emitted row to fill them. Turn off for faster, lighter rows with those fields null.

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

Hard cap on rows emitted (and charged) per run.

## `apiDataGovKey` (type: `string`):

Optional free key from api.data.gov. If the primary SAM.gov search endpoint hardens or drifts, the actor falls back to the official Get Opportunities API using this key.

## Actor input object example

```json
{
  "keywords": [
    "cybersecurity"
  ],
  "naicsCodes": [
    "541511"
  ],
  "noticeTypes": [],
  "setAsideCodes": [],
  "activeOnly": true,
  "modifiedSinceDays": 2,
  "deltaMode": true,
  "includePointOfContact": false,
  "enrichDetails": true,
  "maxResults": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

One row per federal contract notice (new/modified in delta mode) with NAICS, deadlines, set-aside, award and delta fields.

# 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"
    ],
    "naicsCodes": [
        "541511"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sellavir/sam-gov-opportunity-monitor").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"],
    "naicsCodes": ["541511"],
}

# Run the Actor and wait for it to finish
run = client.actor("sellavir/sam-gov-opportunity-monitor").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"
  ],
  "naicsCodes": [
    "541511"
  ]
}' |
apify call sellavir/sam-gov-opportunity-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sellavir/sam-gov-opportunity-monitor"
        }
    }
}

```

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/g0uQcyuNEd0dH8sSx/builds/M6ezCu6Zx98m0g2sh/openapi.json
