# SAM.gov Contract Opportunities Scraper & Monitor - No API Key (`neverempty/sam-gov-opportunities-monitor`) Actor

SAM.gov Contract Opportunities from the official daily file, no API key: filter by keyword, NAICS, PSC, agency, notice type, set-aside, state and dates. One row per notice with deadline, set-aside, place and SAM.gov link. Monitoring returns only new or updated notices.

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

## Pricing

from $1.75 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper & Monitor - No API Key

Get U.S. federal **contract opportunities from SAM.gov** as clean rows: filter by keyword, NAICS, PSC, agency, notice type, set-aside, state and dates, and get one row per notice with its deadline, set-aside, place of performance and SAM.gov link. Turn on **monitoring** and each run returns only the notices that are **new or updated** since your last run.

No SAM.gov account and no API key are needed. The Actor reads the **official daily file** that SAM.gov publishes for exactly this purpose (`ContractOpportunitiesFullCSV.csv` on [sam.gov/data-services](https://sam.gov/data-services)), not the search screens.

### What you should know before you run it

- **Freshness is once a day.** SAM.gov rebuilds the file about once a day (the file read on 2026-10-05 was last modified at 03:30 GMT). A notice posted this morning appears in tomorrow's file. If you need notices within minutes of posting, this Actor is not the right tool. Every row carries `fileLastModified` so you can see how old the data is.
- **Active notices only.** The file holds the notices that are active on SAM.gov (74,620 on 2026-10-05, every one marked active). Archived notices are not in it, so this is not a history of past years.
- **No contact details.** The names, email addresses and phone numbers of contracting officers are not returned. Email addresses and phone numbers written inside the description text are replaced by `[email removed]` / `[phone removed]` (best effort: names written in free text cannot be detected). Open the SAM.gov link of a notice to see who to contact.
- **No attachments.** Solicitation documents are not downloaded. The `url` column opens the notice page where they are listed.

### Input

| Field | What it does |
|---|---|
| `keywords` | Words or phrases to find in the title, solicitation number or description. Not case-sensitive. |
| `keywordMode` | `any` (default): at least one keyword appears. `all`: every keyword appears. |
| `naicsCodes` | NAICS codes, 2 to 6 digits. `541511` is that code; `5415` is every code that starts with 5415. |
| `pscCodes` | Product Service Codes, matched from the start (`R499`, `D3`, `70`). |
| `noticeTypes` | Solicitation, Combined Synopsis/Solicitation, Presolicitation, Sources Sought, Special Notice, Award Notice, Justification and others, as SAM.gov names them. |
| `setAsideCodes` | SBA, SBP, 8A, 8AN, HZC, HZS, SDVOSBC, SDVOSBS, WOSB, WOSBSS, EDWOSB, EDWOSBSS, VSA, ISBEE, IEE, BICiv, LAS, NONE. |
| `agencies` | Text to find in the department, sub-tier or office name (`VETERANS AFFAIRS`, `DEPT OF THE ARMY`, `DEFENSE LOGISTICS AGENCY`). |
| `placeOfPerformanceStates` | Two-letter state codes (`VA`, `CA`). |
| `postedFrom` / `postedTo` | Posted date range, `YYYY-MM-DD`. |
| `deadlineFrom` / `deadlineTo` | Response deadline range, `YYYY-MM-DD`. `deadlineFrom` = today leaves out notices whose deadline has passed. |
| `maxItems` | Most rows per run (default 200, at most 2,000), newest posted first. |
| `includeDescription` | Include the description text (default on). |
| `monitoringMode` | Return only new or updated notices (see below). |
| `resetMonitoringState` | Forget what was remembered for exactly these filters. |

All filters that are set must match. A notice that has no value in a filtered column does not match that filter - see the fill rates below.

Example: small-business IT solicitations that are still open.

```json
{
  "naicsCodes": ["5415"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Sources Sought"],
  "setAsideCodes": ["SBA", "SDVOSBC"],
  "deadlineFrom": "2026-10-06",
  "maxItems": 300
}
```

### Output

One row per notice:

```json
{
  "noticeId": "aa9a6d535c244304bac93aaefe9f5153",
  "title": "USACE SPK DBB Construction - LAR C3B North - Sacramento, CA",
  "solicitationNumber": "W9123827BA001",
  "noticeType": "Presolicitation",
  "baseType": "Presolicitation",
  "department": "DEPT OF DEFENSE",
  "subTier": "DEPT OF THE ARMY",
  "office": "W075 ENDIST SACRAMENTO",
  "postedDate": "2026-10-02 22:53:33",
  "responseDeadline": "2026-10-20T09:00:00-07:00",
  "archiveDate": "2026-11-04",
  "naicsCode": "237990",
  "pscCode": "Y1LC",
  "setAsideCode": "SBA",
  "setAside": "Small Business Set Aside - Total",
  "placeOfPerformanceCity": "Sacramento",
  "placeOfPerformanceState": "CA",
  "placeOfPerformanceZip": null,
  "placeOfPerformanceCountry": "USA",
  "officeCity": "SACRAMENTO",
  "officeState": "CA",
  "officeZip": "95814-2922",
  "officeCountry": "USA",
  "active": true,
  "awardNumber": null,
  "awardDate": null,
  "awardAmountUsd": null,
  "awardee": null,
  "additionalInfoLink": null,
  "url": "https://sam.gov/opp/aa9a6d535c244304bac93aaefe9f5153/view",
  "description": "The United States Army Corps of Engineers (USACE) Sacramento District (SPK) anticipates ...",
  "fileLastModified": "Mon, 05 Oct 2026 03:30:41 GMT",
  "scrapedAt": "2026-10-05T10:02:11.026Z",
  "status": "ok"
}
```

Dates are passed on as SAM.gov writes them: `postedDate` has no time zone in the file, and `responseDeadline` comes in several forms (with a UTC offset, date only, or without an offset). Nothing is converted or guessed. Monitoring rows also carry `change` (`new` or `updated`) and `previousCheckedAt`.

#### How complete the columns are

Counted on the full file of 2026-10-05 (74,620 notices). A missing value is `null`, never a made-up one.

| Column | Filled |
|---|---|
| title, noticeType, postedDate, url | 100% |
| solicitationNumber | 99% |
| department / subTier / office | 98-99% |
| pscCode | 96% |
| naicsCode | 95% |
| responseDeadline | 85% |
| description | 83% |
| setAsideCode | 44% |
| placeOfPerformanceState | 27% |
| award fields (award notices) | 19-21% |

### Monitoring: only new or updated notices

1. The **first run** with a set of filters remembers every matching notice and returns none (run once with monitoring off if you also want what is listed today).
2. **Every later run** with the same filters returns only notices that were not there before:
   - `change: "new"` - a notice whose solicitation number was not seen before;
   - `change: "updated"` - a new notice ID under a solicitation number that was already seen (SAM.gov posts amendments and re-postings as new notice IDs).
3. If SAM.gov has **not published a new file** since your last run, the run stops in about a second without reading the 209 MB file and says so in a row that is not charged.

Schedule it once a day. Each set of filters has its own memory, so you can run several watches side by side. Notices that were held back by `maxItems` or by your maximum charge are not remembered - the next run returns them. Do not run two schedules with identical filters at the same moment.

### Rows that explain an empty or short result

These rows have a `status` other than `ok`, a `note` in plain English, and are never charged as opportunities: `no-results`, `no-new-since-last-run`, `file-not-updated-since-last-run`, `monitoring-baseline-recorded`, `limit-reached`, `budget-reached`, `invalid-input`, `unreadable` (the file could not be downloaded or its columns changed - nothing is guessed).

### Pricing

Pay per event: a small start fee per run that fetches the file, a price per opportunity row returned, and - in monitoring mode only - a small fee for a check that read a new file and found nothing new. Rows that only explain a result are free. See the pricing tab for the current amounts.

### Where the data comes from

The Actor makes one request per run to the public extract `ContractOpportunitiesFullCSV.csv` (SAM.gov Data Services, "Contract Opportunities / datagov"), which SAM.gov's terms of use name as the way to obtain this data in bulk. It does not log in, does not use the search screens and does not need or use an API key. This Actor is not affiliated with SAM.gov or the U.S. General Services Administration.

### Related Actors

- [EU Tender Monitor (TED)](https://apify.com/neverempty/eu-tender-monitor) - public procurement notices from the EU.
- [UK Tenders Scraper](https://apify.com/neverempty/uk-tenders-scraper) - UK public sector tenders.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Keep only notices where these appear in the title, the solicitation number or the description. Not case-sensitive; a keyword of several words is matched as that exact phrase. One per line. Leave empty for no keyword filter.

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

With several keywords: keep a notice when at least one appears (any) or only when every one appears (all).

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

Keep only notices whose NAICS code starts with one of these (2 to 6 digits). 541511 matches that code only; 5415 matches every code that starts with 5415. One per line. Notices with no NAICS code in the file (about 5%) do not match.

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

Keep only notices whose Product Service Code (classification code) starts with one of these, for example R499 or D3. One per line. Notices with no code in the file (about 4%) do not match.

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

Keep only these notice types, as SAM.gov names them. Leave empty for every type (award notices and justifications included).

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

Keep only notices with one of these set-aside codes. More than half of the notices in the file carry no set-aside code at all; those match none of these choices.

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

Keep only notices where one of these texts appears in the department, sub-tier or office name as the file writes them (for example: VETERANS AFFAIRS, DEPT OF THE ARMY, DEFENSE LOGISTICS AGENCY). Not case-sensitive. SAM.gov writes names in its own form (DEPT OF DEFENSE; VETERANS AFFAIRS, DEPARTMENT OF), so use a distinctive part of the name. One per line.

## `placeOfPerformanceStates` (type: `array`):

Keep only notices whose place of performance is in one of these states, as two-letter codes (VA, CA, TX). Only about 27% of notices in the file give a place of performance state; the others do not match.

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

Keep only notices posted on or after this date (YYYY-MM-DD, by the posted date written in the file).

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

Keep only notices posted on or before this date (YYYY-MM-DD).

## `deadlineFrom` (type: `string`):

Keep only notices whose response deadline is on or after this date (YYYY-MM-DD). Use it to leave out notices whose deadline has passed. Notices with no response deadline (about 15%, mostly award notices) do not match.

## `deadlineTo` (type: `string`):

Keep only notices whose response deadline is on or before this date (YYYY-MM-DD).

## `maxItems` (type: `integer`):

The most opportunity rows one run returns (newest posted first). One run returns at most 2,000. If more match, a row that is not charged says how many were left out.

## `includeDescription` (type: `boolean`):

Add the description text of each notice (email addresses and phone numbers in it are replaced by a placeholder). Turn off for smaller rows; keyword filters still look at the description.

## `monitoringMode` (type: `boolean`):

The first run with a set of filters remembers the matching notices and returns none. Every later run with the same filters returns only notices that were not there before, marked new or updated. SAM.gov refreshes the file about once a day; a run that finds the file unchanged stops without reading it. Schedule it once a day.

## `resetMonitoringState` (type: `boolean`):

Clear the remembered notices for exactly these filters before the run, so the run becomes a first monitoring run again.

## Actor input object example

```json
{
  "keywords": [
    "cybersecurity"
  ],
  "keywordMode": "any",
  "maxItems": 50,
  "includeDescription": true,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per SAM.gov contract opportunity notice from the official daily file: title, solicitation number, notice type, department, sub-tier, office, posted date, response deadline, NAICS, PSC, set-aside, place of performance, award fields and the SAM.gov page. Runs with no match, no new notice, an unreadable file or a limit come back as their own rows and are not charged.

# 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"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/sam-gov-opportunities-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"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/sam-gov-opportunities-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"
  ],
  "maxItems": 50
}' |
apify call neverempty/sam-gov-opportunities-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/sam-gov-opportunities-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/9n2TnfPEslCwTPb9h/builds/nuGjnnWDziIb0WuzL/openapi.json
