# SAM.gov Scraper: Federal Contract Opportunities, USD 1/1,000 (`usefulapi/sam-gov-opportunities`) Actor

Federal contract opportunities from SAM.gov's own public daily file: solicitations, sources sought, awards. Filter by NAICS, PSC, set-aside, agency, state, deadline, award; get only new notices since your last run. No API key, no site scraping, contacts off by default. USD 1.00 per 1,000.

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

## Pricing

from $1.00 / 1,000 notices

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 and API

Find US federal contract opportunities on SAM.gov by NAICS code, set-aside, agency, state, deadline and keywords, and get
one clean row per notice: solicitations, combined synopses, presolicitations, sources sought, special notices and award
notices with the winner and the amount. Run it once for a market scan, or every morning on a schedule and get **only the
notices that are new or changed since the last run**.

The data comes from **SAM.gov's own public daily file of all active contract opportunities** (the "Contract Opportunities"
extract on sam.gov/data-services), not from scraping the website. No SAM.gov account, no API key, no proxy, no daily quota.

**USD 1.00 per 1,000 notices + USD 0.005 per run.** A daily monitor that finds 20 new notices costs about USD 0.025 a day.

### Who it's for

- **Small businesses and government contractors:** a morning list of new bids in your NAICS codes and set-aside
  (8(a), HUBZone, SDVOSB, WOSB, small business), with days left to respond.
- **Capture managers and BD teams:** watch target agencies and offices, export to a CRM, Slack or Google Sheets.
- **GovCon consultants and proposal shops:** one run per client, each with its own saved search.
- **Market researchers and subcontractors:** award notices with awardee, amount and date, filtered by agency, code and size.
- **Developers and AI agents:** a federal contract opportunities API with the same columns on every row.

### What you get for each notice

| Group | Fields |
|---|---|
| **Notice** | noticeId, title, solicitationNumber, noticeType, baseType, postedDate, postedDateTime (US Eastern time, as SAM.gov shows it), samUrl |
| **Deadline** | responseDeadline (as SAM.gov states it, with its time zone), responseDeadlineUtc, daysToDeadline, isOpen |
| **Codes** | naicsCode and naicsTitle, pscCode (product and service code) and pscTitle, setAsideCode, setAside, smallBusinessSetAside. Titles come from the official lists: Census NAICS 2022 (2017 for retired codes) and GSA's PSC manual (April 2025) |
| **Buyer** | department, subTier (agency), office, cgac, fpdsCode, aacCode, officeCity, officeState, officeZip, officeCountry |
| **Place of performance** | placeOfPerformanceStreet, placeOfPerformanceCity, placeOfPerformanceState, placeOfPerformanceZip, placeOfPerformanceCountry |
| **Award** | awardNumber, awardDate, awardAmount (a number in USD), awardAmountText (when SAM.gov's amount is not a plain number), awardee (company and address as SAM.gov lists it) |
| **Text** | description (first 1,000 characters by default, up to the full text), descriptionLength, descriptionTruncated, descriptionCutBySam |
| **Status** | active, archiveType, archiveDate (when SAM.gov will mark it inactive), changeType (monitor mode: new or changed), extractDate (the date of SAM.gov's file) |
| **Contacts** (option) | primary and secondary contact title, name, email, phone and fax |

Three ready-made table views: **Opportunities**, **For bidders** and **Awards**. Download as CSV, Excel, JSON or XML, or
read the results through the Apify API.

### Why this one

- **It uses the route SAM.gov offers for this.** SAM.gov's terms of use prohibit "Automated data gathering, web scraping
  tools" on the website and point to its APIs and data extracts instead. This Actor reads only the public extract, so it
  does not depend on the website's internal search, does not get blocked and does not need a proxy.
- **Only new or changed since the last run.** Turn on monitor mode, schedule it daily and each notice reaches you once;
  again only if its deadline, type, set-aside, award or text changes (marked `changed`). Unchanged notices are skipped and
  **not billed**. The memory is kept in your own Apify account.
- **Every filter on the whole active list.** NAICS and PSC codes by prefix, every set-aside code or "any small business",
  notice types, agency or office by name or code, place of performance and office state, posted and deadline windows,
  award amount range, awardee name, exact notice IDs and solicitation numbers, keywords with "exact phrases" and excluded
  words. No cap on how many notices are searched.
- **Amended notices count too.** A notice that was updated in your date window (deadline extended, amendment posted)
  comes back, not only notices first posted then: SAM.gov's file dates each notice by its latest version.
- **Fast and cheap for daily runs.** SAM.gov's file is sorted newest first, so "posted in the last 2 days" reads about the
  first 10 MB of the file and stops, instead of all 206 MB.
- **Clean fields.** NAICS and PSC titles next to the codes, deadline converted to UTC with days left, award amount as a number, set-aside codes made consistent
  (the file mixes `SBA` and `["SBA"]`), public sam.gov links, no HTML.
- **Privacy by default.** The contracting officer contact columns (names, emails, phone numbers) are left out, and email
  addresses and phone numbers are masked wherever else they appear (descriptions, titles, street lines, awardee names).
  Names written inside a description's text are not removed. Turn on "Include contracting officer contacts" if you need
  the contacts to respond.

### Input examples

New IT opportunities for small businesses, posted in the last 2 days, still open:

```json
{
  "naicsCodes": ["541511", "541512", "541519"],
  "setAsides": ["ANY_SMALL_BUSINESS"],
  "postedWithinDays": 2,
  "openOnly": true
}
```

A daily monitor (schedule it once a day; only new or changed notices come back):

```json
{
  "naicsCodes": ["2362", "2382"],
  "states": ["VA", "MD", "DC"],
  "noticeTypes": ["solicitation", "combined", "presolicitation", "sources_sought"],
  "postedWithinDays": 2,
  "onlyNew": true,
  "monitorName": "construction-dmv"
}
```

Large awards from one agency:

```json
{
  "agencies": ["veterans affairs"],
  "noticeTypes": ["award"],
  "minAwardAmount": 1000000,
  "postedWithinDays": 30,
  "openOnly": false
}
```

Keywords: one search per line; words on a line must all appear; quotes for an exact phrase:

```json
{
  "keywords": ["cybersecurity", "\"zero trust\"", "cloud migration"],
  "excludeKeywords": ["construction"],
  "deadlineWithinDays": 21
}
```

### Filters

| Input | What it does |
|---|---|
| `keywords`, `excludeKeywords`, `searchDescription` | Match in the title, and the description unless switched off. Words match from their start ("cyber" finds "cybersecurity"; "IT" does not match "with") |
| `naicsCodes` | 2 to 6 digits, prefix match (`5415` = all computer systems design codes) |
| `pscCodes` | 1 to 4 characters, prefix match (`D3` = IT and telecom services) |
| `setAsides` | SBA, SBP, 8A, 8AN, HZC, HZS, SDVOSBC, SDVOSBS, WOSB, WOSBSS, EDWOSB, EDWOSBSS, VSA, VSS, ISBEE, IEE, BICiv, LAS, ESB, NONE, or ANY_SMALL_BUSINESS |
| `noticeTypes` | solicitation, combined, presolicitation, sources_sought, special_notice, award, justification, modification, surplus, bundle |
| `agencies` | Words from the department, agency or office name, matched at word starts (`army`, `veterans affairs`), or an exact CGAC, FPDS or AAC code |
| `states`, `officeStates` | Place of performance state, contracting office state (2-letter codes) |
| `postedWithinDays`, `postedFrom`, `postedTo` | Posted (or last updated) dates; `postedWithinDays: 1` = yesterday and today |
| `openOnly` | Drop notices whose response deadline has passed (default on; notices without a deadline are kept) |
| `deadlineWithinDays`, `deadlineFrom`, `deadlineTo` | Response deadline window (notices without a deadline are dropped) |
| `minAwardAmount`, `maxAwardAmount`, `awardees` | Award notices by amount and winner |
| `noticeIds`, `solicitationNumbers` | Exact lookups |
| `maxResults` | Stop after this many notices (default 1,000) |
| `onlyNew`, `monitorName`, `includeChanged`, `firstRun` | Monitor mode (see above). `firstRun: "baseline"` remembers what exists today and delivers nothing, so the next runs bring only new notices |
| `descriptionMaxChars` | 0 to 32,003 characters of description (default 1,000) |
| `includeContacts` | Add contracting officer contacts and leave descriptions unmasked (default off) |

All filters combine: a notice must pass every filter you fill in.

### Pricing

Pay per event, nothing else:

| Event | Price |
|---|---|
| Run start | USD 0.005 per run |
| Notice saved | USD 0.001 per notice (USD 1.00 per 1,000) |

Unchanged notices skipped by monitor mode are not billed. Set "Maximum cost per run" on the run and the Actor stops by
itself before it would go over, keeping every notice saved so far.

### Use it through the API

```bash
curl -X POST "https://api.apify.com/v2/acts/usefulapi~sam-gov-opportunities/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"naicsCodes":["541512"],"postedWithinDays":1,"setAsides":["ANY_SMALL_BUSINESS"]}'
```

It also works from the Apify MCP server for AI agents, from Make, Zapier and n8n, and with Apify's Slack, email and Google
Sheets integrations on a schedule.

### FAQ

**Is this an official SAM.gov product?** No. It is an independent tool that reads the public Contract Opportunities data
file SAM.gov publishes for download. Always check the notice on SAM.gov (every row has its `samUrl`) before you bid.

**How fresh is the data?** SAM.gov rebuilds the file once a day, at about 03:30 UTC (11:30 pm US Eastern), so a notice
posted today appears from tomorrow's file on. Each row carries `extractDate`. If you need notices within minutes of
posting, this is not the tool.

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

**What is the difference between noticeId and solicitationNumber?** `noticeId` is SAM.gov's internal ID (the code in the
sam.gov/opp/.../view link). `solicitationNumber` is the agency's number, which SAM.gov's page shows as "Notice ID". One
solicitation can have several notices (presolicitation, solicitation, amendments, award).

**Why are attachments missing?** SAM.gov's public file does not list attachments. Open `samUrl` for the documents.

**Why is placeOfPerformanceState often empty?** About three quarters of notices do not state one. Filter by
`officeStates` to widen a state search.

**Why do some descriptions end early?** SAM.gov's file stores at most 32,003 characters of a description;
`descriptionCutBySam` is true on those rows. The full text is on the notice page.

**Are archived (inactive) notices included?** No. The file holds the notices that are active today, about 70,000, going
back to older notices that are still open.

**Does monitor mode work if I change my filters?** Give the monitor a fixed `monitorName`. Without one, the name is made
from your filters, so changing a filter starts a new memory (and the first run of it delivers everything that matches).

### Limits

- Up to a day behind SAM.gov's website (see above).
- One run reads at most the whole file (about 206 MB, 71,000 notices on 17 Oct 2026) and saves up to 100,000 notices.
- Data is as SAM.gov publishes it: some notices have no NAICS code, no deadline or a place of performance outside the US.

# Actor input Schema

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

One search per line; a notice matches if any line matches. Words on one line must all appear (cyber security = both words); "exact phrase" in quotes. Words match from their start: cyber matches cybersecurity. Searched in the title and the description (see below). Leave empty for all notices.

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

2 to 6 digits; a shorter code matches every code that starts with it (5415 = all computer systems design codes). Leave empty for all industries.

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

Keep notices with any of these set-aside types. Leave empty for all.

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

Leave empty for all types. Award notices carry the awardee, award amount and date.

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

Only notices posted (or last updated) in the last N days; 1 = yesterday and today (SAM.gov's file is a day behind, so today alone would be empty). Fast: the file is newest first, so the run stops reading once it reaches older notices. Empty = any date.

## `openOnly` (type: `boolean`):

Drop notices whose response deadline has passed. Notices without a deadline (most awards, many special notices) are kept.

## `deadlineWithinDays` (type: `integer`):

Only notices with a response deadline from today to N days ahead (notices without a deadline are dropped).

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

Stop after this many notices (newest first). Each saved notice is one billed result.

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

1 to 4 letters or digits; a shorter code matches every code that starts with it (D3 = all IT and telecom services, R4 = professional support).

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

One per line: a word or words from the department, agency or office name, matched at word starts (army, veterans affairs, NASA, naval sea), or an exact CGAC, FPDS or AAC code (097, 2100, W912QR). Short abbreviations only match where SAM.gov writes them (it writes VETERANS AFFAIRS, not VA).

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

2-letter codes (VA, TX, DC). Note: about three quarters of notices give no place of performance; those are dropped by this filter. Use the office state below to widen.

## `officeStates` (type: `array`):

2-letter codes of the buying office's address.

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

Drop notices that match any of these lines (same rules as Keywords).

## `searchDescription` (type: `boolean`):

Off = keywords are matched in the title only (fewer, more precise matches).

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

YYYY-MM-DD. Combined with Posted in the last N days, the later date wins.

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

YYYY-MM-DD.

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

YYYY-MM-DD (the deadline's own date as SAM.gov states it).

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

YYYY-MM-DD.

## `minAwardAmount` (type: `integer`):

Only notices with an award amount at least this large (award notices). Notices without a numeric amount are dropped.

## `maxAwardAmount` (type: `integer`):

Only notices with an award amount up to this.

## `awardees` (type: `array`):

Part of the winning company's name (award notices), one per line.

## `noticeIds` (type: `array`):

Look up exact notices: the 32-character ID in a sam.gov/opp/<ID>/view link.

## `solicitationNumbers` (type: `array`):

Exact solicitation numbers (every notice of a solicitation: presolicitation, solicitation, amendments, award).

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

Monitor mode for schedules: each notice is delivered once, and again only when it changes (deadline moved, award added, type changed). Unchanged notices are skipped and not billed. The memory sits in your own Apify account (key-value store sam-gov-opportunities-monitor).

## `monitorName` (type: `string`):

A name per saved search (for example it-small-business-va). Runs with the same name share one memory. Empty = a name made from your filters (changing a filter then starts a new memory).

## `includeChanged` (type: `boolean`):

Also deliver notices seen before whose deadline, type, set-aside, award or text changed (changeType = changed). Off = new notices only.

## `firstRun` (type: `string`):

What a monitor's first run does.

## `descriptionMaxChars` (type: `integer`):

Characters of each notice's description to keep (cut at a word, with ...). 0 = no description; 32003 = the full text as SAM.gov stores it (SAM.gov itself cuts at 32,003 characters). Same price.

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

Off (default): the contracting officer contact columns are left out, and email addresses and phone numbers are masked in descriptions, titles, street lines and awardee names. Names written inside a description's text (for example "POC: Jane Doe") are not removed. On: adds the primary and secondary contact's title, name, email, phone and fax as SAM.gov publishes them, and nothing is masked. Use contacts only to respond to the notice.

## Actor input object example

```json
{
  "keywords": [],
  "naicsCodes": [
    "5415"
  ],
  "postedWithinDays": 2,
  "openOnly": true,
  "maxResults": 25,
  "searchDescription": true,
  "onlyNew": false,
  "includeChanged": true,
  "firstRun": "deliver",
  "descriptionMaxChars": 1000,
  "includeContacts": false
}
```

# Actor output Schema

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

One row per notice, same columns on every row. Download as CSV, Excel or JSON.

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

Rows saved, new and changed notices (monitor mode), how much of SAM.gov's file was read, the file's date and why the run stopped early (if it did).

# 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": [],
    "naicsCodes": [
        "5415"
    ],
    "postedWithinDays": 2,
    "maxResults": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("usefulapi/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": [],
    "naicsCodes": ["5415"],
    "postedWithinDays": 2,
    "maxResults": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("usefulapi/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": [],
  "naicsCodes": [
    "5415"
  ],
  "postedWithinDays": 2,
  "maxResults": 25
}' |
apify call usefulapi/sam-gov-opportunities --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,usefulapi/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/jOD8YW6tav2GuFYrU/builds/SL01KLBSS3AXehViI/openapi.json
