# SAM.gov Contract Opportunities Scraper (`radar-scraper/sam-gov-opportunities`) Actor

Scrape U.S. federal contract opportunities from SAM.gov by keyword, NAICS, notice type, set-aside and state: deadlines, agency, NAICS, PSC, place of performance and awards. Every notice matches your filters. No personal contact data. Incremental mode. $2.50 per 1,000 notices.

- **URL**: https://apify.com/radar-scraper/sam-gov-opportunities.md
- **Developed by:** [Juan Felipe Henao López](https://apify.com/radar-scraper) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 opportunity scrapeds

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 Scraper

Scrape **U.S. federal contract opportunities from SAM.gov** — solicitations, sources sought, presolicitations and award notices — by **keyword, NAICS, notice type, small-business set-aside and state**. Each notice comes with the agency and office, deadlines, NAICS and PSC codes, place of performance, a clean description snippet and, for award notices, the **award amount and awardee**.

**Every notice you get matches your search.** SAM.gov's own search is full-text and broad (searching "software" also returns valve and hardware orders that only mention software in standard clauses). We check every notice against SAM.gov's own data and drop — and never charge — the ones that don't match. **No API key needed**, and **no personal contact data**.

***

### What you can do

1. **A daily feed of new opportunities in your niche.** Run it every morning with `incrementalMode: true` (for example NAICS `541511` + set-aside `SBA`) and send the new notices to Slack, email, a Google Sheet or your CRM with **n8n**, **Make** or the Apify API. Already-delivered notices are not returned and not charged.
2. **Pipeline research.** Pull every active solicitation for your NAICS codes and states, with deadlines and set-asides, and sort by response date.
3. **Competitive intelligence on awards.** Use `noticeTypes: ["a"]` with `postedWithinDays: 30` to get last month's award notices with the amount, the award number and the awardee (name, UEI, city and state).

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | text | — (example: `software`) | Words to search. **Separate alternatives with commas**: `janitorial, custodial, housekeeping`. A notice is delivered when its title or description contains **all the words of at least one alternative**. Leave empty to search only by the filters below. |
| `keywordsIn` | select | Auto | **Auto:** title only, or title or description when you give NAICS codes. **Title only:** every notice has your keywords in its title. **Title or description:** broader coverage, best combined with NAICS codes; may include notices that only mention your keyword (with "software", at least 2.8× more notices in our test, some of them hardware orders that merely mention software). Title matches always come first. |
| `postedWithinDays` | number | — | Only notices posted in the last N days, e.g. `30` for last month's award notices. |
| `noticeTypes` | list | Solicitation, Combined Synopsis/Solicitation, Presolicitation, Sources Sought | Also Special Notice, Award Notice, Justification (J\&A), Intent to Bundle, Sale of Surplus Property. |
| `naicsCodes` | list | — | 2 to 6 digits; a notice matches when its NAICS starts with one of them (`5415` covers `541511`, `541512`…). |
| `setAside` | select | — | Total Small Business (`SBA`), 8(a), HUBZone, SDVOSB, WOSB, EDWOSB and more. |
| `placeOfPerformanceState` | text | — | Two-letter state code, e.g. `TX`. |
| `activeOnly` | yes/no | yes | Only notices still open. |
| `includeDetails` | yes/no | yes | Reads each notice's detail record (NAICS, PSC, set-aside, place of performance, award, full description). Always on when you filter by NAICS, set-aside or state, because those filters are verified on the detail. |
| `maxItems` | number | 50 | Maximum notices to return and charge (up to 5,000). |
| `incrementalMode` | yes/no | no | Only notices not delivered by a previous run of the same search. |

**Auto:** without NAICS codes your keywords must be in the title; with NAICS codes they may also be in the description (in our tests, description-only matches were off-topic 3 times out of 4 without NAICS codes and about 1 in 10 with them). The status message says which mode was used.

**Write both spellings.** SAM.gov's search does not join or split words: `cybersecurity` does not find "Cyber Security" titles. Write the variants as alternatives: `cybersecurity, cyber security`.

### Output

One record per notice. Example (a solicitation; values abbreviated):

```json
{
  "noticeId": "4f1c…",
  "title": "Custodial Services at TX190, Denton, TX",
  "solicitationNumber": "36C25726Q0045",
  "noticeTypeCode": "o",
  "noticeType": "Solicitation",
  "isActive": true,
  "postedDate": "2026-10-14T15:02:11+00:00",
  "responseDeadline": "2026-10-28T16:00:00+00:00",
  "responseTimeZone": "America/Chicago",
  "department": "VETERANS AFFAIRS, DEPARTMENT OF",
  "agency": "VETERANS AFFAIRS, DEPARTMENT OF",
  "office": "257-NETWORK CONTRACT OFFICE 17 (36C257)",
  "naicsCodes": ["561720"],
  "pscCode": "S201",
  "setAsideCode": "SDVOSBC",
  "placeOfPerformance": { "city": "Denton", "state": "TX", "zip": "76201", "country": "USA" },
  "award": null,
  "descriptionSnippet": "The Department of Veterans Affairs intends to award a contract for custodial services…",
  "keywordMatchedIn": "TITLE",
  "matchedKeywords": "custodial",
  "url": "https://sam.gov/opp/4f1c…/view"
}
```

#### How the fields are filled

- **`keywordMatchedIn` / `matchedKeywords`**: where SAM.gov's data contains your keywords (`TITLE` or `DESCRIPTION`) and which alternative matched. Split and joined spellings match each other ("Cyber Security" matches `cybersecurity`). SAM.gov also expands synonyms (searching "janitorial" returns "Custodial Services"); list the variants you accept as comma-separated alternatives.
- **Order:** title matches first, then description matches, each in SAM.gov's relevance order. In incremental mode, most recently modified first, so new notices come first.
- **`award`** (award notices): date, amount in USD, award number, awardee name, UEI, city and state. SAM.gov stores the amount as typed text; when it holds several amounts ("$48,138.23 (Base Year) / $289,121.11 (Options)"), `amountUSD` is `null` and `amountText` has the original text. The awardee's street address is never delivered: when SAM.gov typed it into the name, we keep only the name, or `null` when the name cannot be separated safely (the UEI still identifies the company).
- **Contacts and addresses:** contracting officer contacts are not included; each notice links to its SAM.gov page. The description snippet is scrubbed of emails, phone numbers and street addresses.
- **Data as published by SAM.gov:** the place of performance is missing on about half of the notices; set-aside is `null` when there is none. In our sample of 581 award notices, the amount was present on 99%, the awardee name on 100% and the UEI on 98%; the awardee's city and state on 56%, because SAM.gov publishes no location for the rest.

### Pricing

**$2.50 per 1,000 notices** ($0.0025 per notice), plus Apify's standard small fee per run start. You only pay for notices delivered to your dataset: notices that don't match your search and, in incremental mode, notices you already received are **not charged**.

| Example | Cost |
|---|---|
| 100 notices | ≈ $0.25 |
| Daily alert with ~40 new notices | ≈ $0.10 per day |

### Limitations

- The public search returns at most 10,000 notices per search; narrow it with NAICS, notice type or state for very broad terms.
- When a run delivers fewer notices than `maxItems`, its status message says why: "no more results for this search", or "relevant results exhausted for this search" when the following pages stopped containing matches (title only: 10 pages of 100 in a row without a title match; title or description: 2 pages in a row under 20%). You are not charged for unrelated results. When keywords are matched in titles only, it also says how many notices had them in the title and suggests adding NAICS codes.
- Attachments (bid documents) are not downloaded; each notice links to its SAM.gov page.

### Use it from code, n8n or Make

```bash
curl -X POST "https://api.apify.com/v2/acts/radar-scraper~sam-gov-opportunities/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"keywords": "cybersecurity, cyber security", "setAside": "SBA", "maxItems": 100, "incrementalMode": true}'
```

### Feedback

If this Actor saves you time, a short review helps other buyers find it. Something not working? Open an issue on the Actor page and I answer within 48 hours.

### ⚖️ Legal & data notice

This Actor reads publicly available information on SAM.gov, the U.S. government's official contracting site, as any anonymous visitor can. It does not log in or access private areas, and it does not deliver personal contact data of government employees. You are responsible for how you use the data and for complying with applicable laws.

# Actor input Schema

## `keywords` (type: `string`):

Words to search, with alternatives separated by commas (e.g. "janitorial, custodial, housekeeping" or "cloud software"). Every notice delivered contains ALL the words of at least one alternative in its title or description; SAM.gov's search also returns synonyms and unrelated notices, which are dropped and not charged. Leave empty to search by NAICS, notice type or set-aside only.

## `keywordsIn` (type: `string`):

Auto (default): title only, or title or description when you give NAICS codes. Title only: every notice has your keywords in its title. Title or description: broader coverage, best combined with NAICS codes; may include notices that only mention your keyword (e.g. a drone order that must meet "cybersecurity" standards). Title matches always come first.

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

Optional. Only notices posted in the last N days (e.g. 30 for last month's award notices).

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

Types of notice to include.

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

Optional NAICS codes (2 to 6 digits, e.g. 541511). A notice is delivered when its NAICS starts with one of them.

## `setAside` (type: `string`):

Optional small-business set-aside.

## `placeOfPerformanceState` (type: `string`):

Optional two-letter U.S. state code (e.g. TX).

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

Only notices that are still open.

## `includeDetails` (type: `boolean`):

Read each notice's detail record: NAICS, PSC, set-aside, place of performance, award and the full description. Always on when filtering by NAICS, set-aside or state, because those filters are verified on the detail.

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

Maximum number of notices to return and charge.

## `incrementalMode` (type: `boolean`):

Skip notices delivered by a previous run of the same search: they are not returned and not charged.

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

Optional name for the incremental history.

## `resetIncrementalState` (type: `boolean`):

Delete the incremental history and start over.

## `proxyConfiguration` (type: `object`):

Optional. Apify datacenter proxies by default.

## Actor input object example

```json
{
  "keywords": "software",
  "keywordsIn": "auto",
  "noticeTypes": [
    "o",
    "k",
    "p",
    "r"
  ],
  "naicsCodes": [],
  "activeOnly": true,
  "includeDetails": true,
  "maxItems": 30,
  "incrementalMode": false,
  "resetIncrementalState": false
}
```

# Actor output Schema

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

No description

## `outputStats` (type: `string`):

No description

## `runReport` (type: `string`):

No description

# 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": "software",
    "keywordsIn": "auto",
    "noticeTypes": [
        "o",
        "k",
        "p",
        "r"
    ],
    "maxItems": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("radar-scraper/sam-gov-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": "software",
    "keywordsIn": "auto",
    "noticeTypes": [
        "o",
        "k",
        "p",
        "r",
    ],
    "maxItems": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("radar-scraper/sam-gov-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": "software",
  "keywordsIn": "auto",
  "noticeTypes": [
    "o",
    "k",
    "p",
    "r"
  ],
  "maxItems": 30
}' |
apify call radar-scraper/sam-gov-opportunities --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,radar-scraper/sam-gov-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/D6UZEFfiZaAgUkxK7/builds/qUQq1duNmOUoDPBsq/openapi.json
