# Sam Gov Opportunities Scraper (`normdata/sam-gov-opportunities-scraper`) Actor

Get US federal contract opportunities from SAM.gov with NAICS, PSC, set-asides, contacts, attachments and full descriptions. Search solicitations, monitor new or amended notices, or track contracts awarded to companies and agencies. No API key or login required.

- **URL**: https://apify.com/normdata/sam-gov-opportunities-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.50 / 1,000 results

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?

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

![Norm Data](https://i.ibb.co/rGbhM5Y8/Chat-GPT-Image-Sep-8-2026-02-20-50-PM.png)

## 🏛️ SAM.gov Opportunities Scraper

Pull **US federal contract opportunities** from **SAM.gov** as one flat row each: solicitations,
combined synopses, sources-sought notices and award notices, with **NAICS, PSC, set-aside,
contracting-officer contact, place of performance, attachment links and the full cleaned
description** already resolved. Search the board, run a **change-detection monitor** for a daily
feed of what is new or amended, or run **awards mode** to track every contract a company or an
agency has won. No API key, no login.

Here is one real row (trimmed), a US Air Force combined synopsis:

```json
{
  "notice_id": "b9166d40f0264cbca811fd73bce06885",
  "solicitation_number": "FA860126Q0074",
  "title": "Brand Name Agilent High-Performance Liquid Chromatograph-Mass Spectrometer",
  "notice_type_code": "k",
  "notice_type": "Combined Synopsis/Solicitation",
  "status": "active",
  "posted_date": "2026-09-01T13:00:00Z",
  "response_deadline": "2026-09-19T17:00:00Z",
  "last_modified": "2026-09-08T18:22:00Z",
  "amendment_count": 1,
  "agency_department": "DEPT OF DEFENSE",
  "agency_subtier": "DEPT OF THE AIR FORCE",
  "agency_office": "FA8601 AFLCMC PZIO",
  "naics_primary": "334516",
  "naics_codes": ["334516"],
  "psc_code": "6630",
  "set_aside_code": null,
  "set_aside": null,
  "pop_city": "Wright Patterson AFB",
  "pop_state": "OH",
  "pop_zip": "45433",
  "pop_country": "USA",
  "contact_name": "Alejandro Quiles",
  "contact_email": "alejandro.quiles_sanchez.1@us.af.mil",
  "contact_secondary_email": "candice.snow@us.af.mil",
  "description": "Combined Synopsis/Solicitation: Brand Name Agilent High-Performance Liquid Chromatograph-Mass Spectrometer ...",
  "award_amount": null,
  "awardee_name": null,
  "attachments": [
    { "name": "Solicitation - FA860126Q0074.pdf", "url": "https://sam.gov/api/prod/opps/v3/opportunities/resources/files/04aa11e0.../download", "type": ".pdf", "size": 556871 }
  ],
  "url": "https://sam.gov/opp/b9166d40f0264cbca811fd73bce06885/view",
  "scraped_at": "2026-09-09T15:43:35.748Z"
}
```

### What it does

Reads SAM.gov's own opportunity search and detail endpoints (the same ones the sam.gov website
calls). Four modes:

- **Search** - filter the board by keyword, NAICS, notice type and set-aside, and get a full row
  for every match, including the resolved detail record (NAICS, PSC, set-aside, contracting-officer
  contact, place of performance, award data) and the cleaned description.
- **Monitor** - give it a date or a relative window (`"2 days"`, `"1 week"`) and it returns
  **only** the opportunities posted or amended since then, each flagged `new` or `modified`. Built
  for a scheduled run that feeds a "what changed today" digest.
- **Awards** - filter to award notices for a company (by name or UEI), a keyword or a NAICS code,
  and get the award number, amount, date and awardee UEI on every row. A competitor-intelligence
  feed: who is winning what, at which agency, for how much.
- **Look up** - paste exact SAM.gov notice ids and get the full record for each.

### Why this scraper

- **A real monitor mode.** Point it at "modified since yesterday" on a schedule and it stops
  walking the board as soon as it passes your cutoff, so a daily feed is a few seconds and a
  handful of rows, not a full re-scrape. Every row is tagged `new` or `modified`.
- **A dedicated awards / competitor-intelligence mode.** One input - a company name - returns
  every federal contract that company has been awarded through SAM.gov, with dollar amounts and
  the awarding office. Server-side pre-filtered, so it is fast even against the full board.
- **Detail resolved, not just the search snippet.** NAICS, PSC, set-aside, place of performance,
  the contracting officer's name, email and phone, and the full description - all on the row.
- **Attachment links.** The SOW, specs and amendment PDFs with direct, working download URLs and
  file sizes.
- **Descriptions cleaned.** HTML stripped, entities decoded, whitespace collapsed - not a wall of
  `<p>` tags.
- **One row per look-up input.** A notice id that does not resolve comes back as an `error` row
  so your output lines up with your list.
- **No API key.** SAM.gov's public endpoints, no registration, no rate-limit token.

### How it compares

| Capability | This actor | Other SAM.gov scrapers on Apify |
|---|:--:|:--:|
| Search opportunities by keyword / NAICS / type | yes | yes |
| Full detail record (NAICS, PSC, set-aside, place of performance) | yes | some |
| Contracting-officer name + email + phone | yes | some |
| Attachment links with download URLs + sizes | yes | rare |
| Cleaned plain-text description | yes | often raw HTML |
| **Monitor mode (only new / modified since a date, tagged)** | **yes** | **no** |
| **Awards mode (contracts won by a company or agency, with $)** | **yes** | **no** |
| Look up by notice id, with error rows for bad ids | yes | rare |
| Declared dataset schema + free-plan preview | yes | rare |

### Use cases

- **Pipeline / capture.** A daily monitor feed of new solicitations in your NAICS codes, straight
  into a CRM or a Slack channel, with the contracting officer's email already on the row.
- **Competitor intelligence.** Awards mode on a rival's name: every contract they have won,
  the agency, the amount, the date - a win/loss picture over time.
- **Market sizing.** Search a NAICS code across the whole board and total the award amounts to
  size an agency's spend in your space.
- **Teaming.** Find set-aside opportunities (8(a), HUBZone, SDVOSB, WOSB) you qualify for, or
  find the small businesses winning them to team with.
- **Compliance / archive.** Look-up mode to snapshot the full record and attachments of a
  solicitation you are bidding, before it is amended or archived.

### Quickstart

Search - IT solicitations open now:

```json
{
  "mode": "search",
  "keyword": "cybersecurity",
  "naicsCodes": ["541512", "541519"],
  "noticeTypes": ["o", "k"],
  "activeOnly": true,
  "fetchDetails": true,
  "maxItems": 100
}
```

Monitor - everything new or amended in the last 2 days:

```json
{ "mode": "monitor", "naicsCodes": ["236220"], "modifiedSince": "2 days", "maxItems": 500 }
```

Awards - every contract Booz Allen has won:

```json
{ "mode": "awards", "awardee": "Booz Allen", "maxItems": 200 }
```

Look up - one notice by id:

```json
{ "mode": "lookup", "noticeIds": ["b9166d40f0264cbca811fd73bce06885"], "includeAttachments": true }
```

### Input reference

| Field | Applies to | Description |
|---|---|---|
| `maxItems` | all | Ceiling on rows written. |
| `mode` | all | `search`, `monitor`, `awards` or `lookup`. |
| `keyword` | search, monitor, awards | Full-text search over title, description, solicitation number (and awardee name in awards mode). |
| `naicsCodes` | search, monitor, awards | Industry codes, e.g. `541512`. A 2-digit prefix matches the whole sector. |
| `noticeTypes` | search, monitor | `o` Solicitation, `k` Combined, `p` Presolicitation, `r` Sources Sought, `s` Special Notice, `a` Award, `u` Justification. |
| `setAsides` | search, monitor | Small-business set-asides: `SBA`, `8A`, `HZC`, `SDVOSBC`, `WOSB`, `EDWOSB`, ... |
| `activeOnly` | search, monitor | Exclude archived and closed opportunities. |
| `fetchDetails` | all | Resolve the full detail record (NAICS, PSC, set-aside, contact, place of performance, award). One extra request per opportunity. |
| `includeAttachments` | all | Add attachment links with download URLs. One extra request per opportunity. |
| `modifiedSince` | monitor | ISO date (`2026-09-01`) or a relative window (`"2 days"`, `"1 week"`). Required for monitor mode. |
| `awardee` | awards | Company name to match on award notices. Partial, case-insensitive. |
| `awardeeUei` | awards | Exact SAM.gov Unique Entity ID of the awardee. |
| `noticeIds` | lookup | 32-character SAM.gov notice ids. Required for look-up mode. |

### Output reference (selected)

One row per opportunity. Values are `null` when SAM.gov does not publish that field for the
notice, never invented.

| Field | Description |
|---|---|
| `notice_id` / `solicitation_number` / `title` | Identity. |
| `notice_type_code` / `notice_type` | `o` / `k` / `r` / `a` ... and the human label. |
| `status` | `active`, `inactive`, `archived` or `cancelled`. |
| `posted_date` / `response_deadline` / `last_modified` / `amendment_count` | Dates and revision count. |
| `agency_department` / `agency_subtier` / `agency_office` | The organisation hierarchy, split into three levels. |
| `naics_primary` / `naics_codes` / `psc_code` | Classification. |
| `set_aside_code` / `set_aside` | Small-business set-aside, code and label. |
| `pop_city` / `pop_state` / `pop_zip` / `pop_country` | Place of performance. |
| `contact_name` / `contact_email` / `contact_phone` / `contact_secondary_email` | Contracting officer. |
| `award_number` / `award_amount` / `award_date` / `awardee_name` / `awardee_uei` | Award notices. |
| `description` | Full text, HTML stripped and entities decoded. |
| `attachments` | List of `{ name, url, type, size }` - `url` is a direct download link. |
| `change_type` | `new` or `modified` - monitor mode only. |
| `query` / `error` | Set on an unresolved look-up id row. |
| `url` | The sam.gov page for the opportunity. |
| `scraped_at` | ISO 8601 collection timestamp. |

### Run via API and CLI

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~sam-gov-opportunities-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"monitor","naicsCodes":["541512"],"modifiedSince":"1 day","maxItems":500}'
```

```bash
apify call USERNAME/sam-gov-opportunities-scraper --input '{"mode":"awards","awardee":"Leidos","maxItems":200}'
```

### Fetch results

```bash
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
```

`<DATASET_ID>` is `defaultDatasetId` on the run object.

### Billing and limits

- **Pay per result.** Billed per opportunity row written. The current rate is on the Actor's
  Pricing tab.
- **No charge on failure.** A run that produces zero rows fails with a message and is not billed.
- An unresolved look-up id still writes one `error: "not found"` row and is billed as a base row.
- **Free Apify plans** run the built-in 10-row sample only. Upgrade to run your own settings.
- SAM.gov's search endpoint returns at most the first 10,000 rows of a result set. Narrow the
  filters (add a NAICS code, a date window, a notice type) if a search matches more than that.
- `fetchDetails` and `includeAttachments` each add one request per opportunity. Leaving both off
  gives a fast, shallow scrape from the search endpoint alone.

### FAQ and troubleshooting

**Do I need an API key or a SAM.gov account?** No. This uses the public endpoints the sam.gov
website itself calls. There is no key, no login and no captcha.

**Is this the same as SAM.gov's official Get Opportunities API?** No. The official API needs a
registered api.data.gov key and rate-limits it. This Actor reads the site's own search and
detail endpoints, so it needs no key - and it resolves the full detail record and attachments,
which the basic official feed does not.

**How does monitor mode know what changed?** It sorts the board by most-recently-modified and
walks from the top, stopping as soon as it reaches an opportunity older than your `modifiedSince`.
Each row is tagged `new` if it was first posted in the window, `modified` if it was only amended.

**Why is a field `null`?** SAM.gov did not publish it for that notice. Presolicitation notices
often have no set-aside or place of performance yet; only award notices have award data. Missing
values are `null`, never guessed.

**Can I get more than 10,000 results?** Not in one run - that is SAM.gov's limit. Split the
search by NAICS code, agency or a date window and run each slice.

**Is this an official SAM.gov tool?** No. Independent, not affiliated with SAM.gov or the US
government. It reads only data SAM.gov publishes openly.

### Local development

```bash
bun install
bun test              # offline: normalisation and input logic
bun run src/main.ts   # reads storage/key_value_stores/default/INPUT.json
bun scripts/verify-live.mjs   # hits sam.gov directly, checks all four modes
```

# Actor input Schema

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

Caps how many opportunity rows this run writes. Starts at 10 for a quick sample; raise it for a full run.

## `mode` (type: `string`):

Search filters the board. Monitor returns only opportunities posted or amended since a date (for scheduled daily feeds). Awards filters to award notices. Look up takes exact notice ids.

## `keyword` (type: `string`):

Full-text search across title, description, solicitation number and (for awards) awardee name.

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

Industry codes, e.g. 541512 (computer systems design), 236220 (commercial building construction). A 2-digit prefix matches the whole sector.

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

Restrict to these notice types. Leave empty for all.

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

Only opportunities with one of these small-business set-asides.

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

Exclude archived and closed opportunities.

## `fetchDetails` (type: `boolean`):

Add NAICS, PSC, set-aside, contracting-officer contact, place of performance, award amount and the full description. One extra request per opportunity.

## `includeAttachments` (type: `boolean`):

Add the list of attached files (SOW, specs, amendments) with download URLs. One extra request per opportunity.

## `modifiedSince` (type: `string`):

ISO date (2026-09-01) or a relative window ("2 days", "1 week"). Monitor returns only opportunities posted or amended on or after this, each flagged new or modified.

## `awardee` (type: `string`):

Company name to match on award notices, e.g. "Booz Allen". Partial, case-insensitive.

## `awardeeUei` (type: `string`):

Exact SAM.gov Unique Entity ID of the awardee.

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

32-character SAM.gov notice ids (from a listing's notice\_id or url). Ids that do not resolve come back as an error row.

## Actor input object example

```json
{
  "maxItems": 10,
  "mode": "search",
  "keyword": "cybersecurity",
  "naicsCodes": [
    "541512",
    "541519"
  ],
  "noticeTypes": [
    "o",
    "k"
  ],
  "activeOnly": true,
  "fetchDetails": true,
  "includeAttachments": false
}
```

# Actor output Schema

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

One dataset row per opportunity, with NAICS, PSC, set-aside, contracting-officer contact, place of performance, award data, attachments and the cleaned 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 = {
    "maxItems": 10,
    "mode": "search",
    "keyword": "cybersecurity",
    "naicsCodes": [
        "541512",
        "541519"
    ],
    "noticeTypes": [
        "o",
        "k"
    ],
    "activeOnly": true,
    "fetchDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/sam-gov-opportunities-scraper").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 = {
    "maxItems": 10,
    "mode": "search",
    "keyword": "cybersecurity",
    "naicsCodes": [
        "541512",
        "541519",
    ],
    "noticeTypes": [
        "o",
        "k",
    ],
    "activeOnly": True,
    "fetchDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/sam-gov-opportunities-scraper").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 '{
  "maxItems": 10,
  "mode": "search",
  "keyword": "cybersecurity",
  "naicsCodes": [
    "541512",
    "541519"
  ],
  "noticeTypes": [
    "o",
    "k"
  ],
  "activeOnly": true,
  "fetchDetails": true
}' |
apify call normdata/sam-gov-opportunities-scraper --silent --output-dataset

```

## MCP server setup

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

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/HUav4C2614pdfbtIk/builds/buxVn3JRMJdLI92RY/openapi.json
