# SAM.gov Contract Opportunities API Scraper (`knotty_mistveil/sam-gov-opportunities`) Actor

US federal contract opportunities from the official SAM.gov API: solicitations, sources sought, awards, set-asides, NAICS, deadlines, contacts.

- **URL**: https://apify.com/knotty\_mistveil/sam-gov-opportunities.md
- **Developed by:** [Jason](https://apify.com/knotty_mistveil) (community)
- **Categories:** Business, Lead generation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 result returneds

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 Contract Opportunities API Scraper

Every US federal contract opportunity — solicitations, combined synopses, presolicitations, sources sought, special notices and award notices — pulled straight from the **official SAM.gov Get Opportunities Public API v2** and delivered as flat, ready-to-use JSON, CSV or Excel. No HTML scraping and no unofficial mirror: the Actor calls the government endpoint that SAM.gov publishes for exactly this purpose, so what you get is what the contracting officer posted, including NAICS and PSC codes, set-aside program, response deadline, place of performance, award amounts and the contracting officer's name, email and phone. Point it at a date window, narrow it by industry or agency, and get a clean pipeline of federal bid leads into your CRM, spreadsheet or database.

You bring your own free SAM.gov API key (see [Data source and licence](#data-source-and-licence)); the Actor keeps its request count to the minimum so a personal key goes a long way.

### What you get

One record per notice, flat camelCase fields:

- **Identity and link** — `id` (SAM.gov noticeId), `source`, `url` (`https://sam.gov/opp/{id}/view`), `uiLink`, `solicitationNumber`, `fetchedAt`
- **The notice** — `title`, `noticeType`, `baseType`, `postedDate`, `responseDeadline`, `archiveType`, `archiveDate`, `active`
- **Classification** — `naicsCode`, `naicsCodes` (all codes on the notice), `classificationCode` (PSC), `setAside`, `setAsideDescription`
- **Buying agency** — `agencyPath` (full department → sub-tier → office path), `agencyPathCode`, `department`, `subTier`, `office`, `organizationType`, `officeCity`, `officeState`, `officeZip`, `officeCountry`
- **Where the work happens** — `placeOfPerformance` (one readable line), `popCity`, `popState`, `popZip`, `popCountry`
- **Who to contact** — `pointOfContact` (full array as published) plus flattened `primaryContactName`, `primaryContactEmail`, `primaryContactPhone`
- **Award notices** — `awardDate`, `awardNumber`, `awardAmount` (numeric), `awardeeName`, `awardeeUei`, `awardeeCity`, `awardeeState`
- **Documents** — `descriptionLink`, `description` (full notice text, optional), `additionalInfoLink`, `resourceLinks` (attachment URLs)

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `apiKey` | string (secret) | — | **Required.** Your own free SAM.gov public API key. Falls back to the `SAM_API_KEY` environment variable. |
| `daysBack` | integer | `7` | Fetch notices posted in the last N days. Ignored when `dateFrom` and `dateTo` are both set. |
| `dateFrom` / `dateTo` | string | — | Explicit posted-date window, `YYYY-MM-DD`. Max one year apart (a SAM.gov rule). |
| `noticeTypes` | array | `[]` (all) | Notice type codes: `o` Solicitation, `k` Combined Synopsis/Solicitation, `p` Presolicitation, `r` Sources Sought, `a` Award Notice, `s` Special Notice, `u` Justification, `i` Intent to Bundle, `g` Sale of Surplus. Full names such as `Sources Sought` also work. |
| `keywords` | array | `[]` | Case-insensitive OR match over title, solicitation number, agency, NAICS, PSC, set-aside and awardee. A single keyword is pushed to the API as a title search; several are matched locally. |
| `title` | string | — | Server-side SAM.gov title search. |
| `naicsCodes` | array | `[]` | e.g. `541512`. One full 6-digit code is sent to the API; several codes — or prefixes like `5415` — are matched locally against every NAICS code on the notice. |
| `setAsides` | array | `[]` | Set-aside codes, e.g. `SBA`, `8A`, `HZC`, `SDVOSBC`, `WOSB`, `EDWOSB`, `VSA`. |
| `classificationCode` | string | — | Product/Service Code (PSC), e.g. `D310`. |
| `state` | string | — | Two-letter place-of-performance state, e.g. `TX`. |
| `zip` | string | — | Place-of-performance ZIP. |
| `organizationName` | string | — | Contracting organization name as SAM.gov spells it. |
| `solicitationNumber` | string | — | Fetch one solicitation by number. |
| `noticeId` | string | — | Fetch one notice by its SAM.gov id. |
| `responseDeadlineFrom` / `responseDeadlineTo` | string | — | Response-deadline window, `YYYY-MM-DD`. Use `responseDeadlineFrom` = today to skip closed notices. |
| `activeOnly` | boolean | `false` | Drop notices SAM.gov has archived. |
| `fetchDescriptions` | boolean | `false` | Also download the full notice text into `description`. Costs one extra API request **per record** — see [Limits and freshness](#limits-and-freshness). |
| `maxItems` | integer | `500` | Stop after this many results. This is also your cost cap. |

Example input — open small-business IT solicitations in Texas from the last two weeks:

```json
{
  "apiKey": "<your SAM.gov key>",
  "daysBack": 14,
  "noticeTypes": ["o", "k"],
  "naicsCodes": ["541512"],
  "setAsides": ["SBA"],
  "state": "TX",
  "activeOnly": true,
  "maxItems": 200
}
```

### Output example

> **Illustrative — not a live capture.** `api.sam.gov` is geo-restricted and answers every request
> from the machine this Actor was built on with an empty HTTP 404, so no record below came off the
> wire here. The JSON is GSA's own published example notice from the Get Opportunities API
> documentation, passed through this Actor's `normalizeRecord()`, so the field names, types and
> nesting are exactly what a run produces (`pointOfContact` and a few empty fields trimmed for
> length). Run the Actor with your own free key to see genuine records.

```json
{
  "id": "5b345bbb7127b91a3ad577b203fc6f68",
  "source": "sam.gov",
  "url": "https://sam.gov/opp/5b345bbb7127b91a3ad577b203fc6f68/view",
  "title": "Historic Office Renovation",
  "solicitationNumber": "47PF0018R0023",
  "noticeType": "Award Notice",
  "baseType": "Combined Synopsis/Solicitation",
  "postedDate": "2018-05-04",
  "responseDeadline": null,
  "archiveType": "manual",
  "naicsCode": "236220",
  "naicsCodes": ["236220"],
  "classificationCode": "Z",
  "setAside": null,
  "setAsideDescription": null,
  "agencyPath": "GENERAL SERVICES ADMINISTRATION.FEDERAL ACQUISITION SERVICE.GSA/FAS CENTER FOR IT SCHEDULE PROG",
  "agencyPathCode": "047.4732.47QTCA",
  "department": "GENERAL SERVICES ADMINISTRATION",
  "subTier": "PUBLIC BUILDINGS SERVICE",
  "office": "PBS R5",
  "organizationType": "OFFICE",
  "officeCity": "CHICAGO",
  "officeState": "IL",
  "officeZip": "60604",
  "placeOfPerformance": "517 E Wisconsin Ave, Milwaukee, WI 53202, USA",
  "popCity": "Milwaukee",
  "popState": "WI",
  "popZip": "53202",
  "primaryContactName": "Jesse L. Jones",
  "primaryContactEmail": "jesse.jones@gsa.gov",
  "primaryContactPhone": "2174941263",
  "awardDate": "2018-05-04",
  "awardNumber": "47PF0018C0066",
  "awardAmount": 800620,
  "awardeeName": "D.G. Beyer, Inc.",
  "awardeeUei": "025114695AST",
  "awardeeCity": "New Berlin",
  "awardeeState": "WI",
  "descriptionLink": "https://api.sam.gov/prod/opportunities/v1/noticedesc?noticeid=5b345bbb7127b91a3ad577b203fc6f68",
  "active": true,
  "fetchedAt": "2026-09-07T06:12:44.183Z"
}
```

Every run also writes a `SUMMARY` record to the key-value store with `pushed`, `scanned`, `filteredOut`, `totalRecordsInWindow`, `requests` (SAM.gov API calls used) and the resolved date window.

### Pricing

Pay per result: **$0.003 per opportunity returned**, i.e. **$3.00 per 1,000 results**. Nothing is charged for a run that returns no results, and `maxItems` caps your spend exactly. Apify's free plan includes monthly usage credit, so small runs typically cost you nothing beyond that allowance; larger runs draw on your Apify plan as usual.

### Data source and licence

- **Source:** [SAM.gov Get Opportunities Public API v2](https://open.gsa.gov/api/get-opportunities-public-api/), operated by the US General Services Administration — `https://api.sam.gov/opportunities/v2/search`.
- **Licence:** SAM.gov contract opportunity notices are works of the US federal government, not subject to domestic copyright, and are published as open data (see the [Data.gov data policy](https://www.data.gov/privacy-policy#data_policy)). Use is subject to the SAM.gov terms of use and the GSA API terms on the page linked above. This Actor neither modifies nor re-hosts the data; it reformats the API response.
- **Your API key:** sign in at [sam.gov](https://sam.gov), open **Account Details** and choose **Request API Key** (the "Public API Key" on your profile). It is free, arrives immediately, and belongs to you — enter it in the `apiKey` field, where Apify stores it encrypted. It is never printed to the run log; the Actor redacts it from every URL and error message.
- **No affiliation:** this Actor is an independent client of a public API. It is not endorsed by or affiliated with GSA or SAM.gov.

### Limits and freshness

- **Freshness:** notices appear here as soon as SAM.gov publishes them to the API — usually within minutes of the contracting officer posting. Schedule the Actor daily with `daysBack: 2` for a reliable overlap.
- **Your daily key quota is the real limit.** GSA rate-limits by role: **10 requests per day for a non-federal personal key**, 1,000 per day for a federal user; entity-linked and system accounts get more. This Actor is built around that: it fetches **1,000 records per request** (the API maximum), makes exactly one request when no local filter is needed, and never re-requests a page. Ten requests therefore reach up to 10,000 notices a day.
- **`fetchDescriptions` is the expensive option.** SAM.gov serves the full notice text from a separate endpoint, one record at a time, and each download counts against the same daily quota. With a personal key, keep `maxItems` under 10 when you turn it on. Without it, you still get `descriptionLink` for every record.
- **Window rules:** `postedFrom` and `postedTo` are mandatory at SAM.gov and may not be more than one year apart; the Actor validates this before spending a request.
- **Filters:** single-value filters (one NAICS code, one set-aside, one title/keyword, state, ZIP, PSC, organization) are pushed to the API so it does the work. Multi-value filters are applied locally, which needs full 1,000-record pages — expect more requests when you combine several.
- **If a run returns nothing:** SAM.gov answers HTTP 404 with an empty body both when no notice matches and when the caller's region is blocked. The Actor logs this clearly. Apify's US and EU runners are fine; some other regions are not.
- **Paging mode, still unverified against the live API.** GSA documents `offset` as a *page index*, but some deployments treat it as a *record offset*. The Actor starts in the documented page-index mode and switches to record-offset mode when a second page comes back mostly duplicated (`Detected record-offset paging` in the log); both branches are covered by `test/unit.test.js` and `test/e2e.test.js` against a stubbed transport, and neither has yet been confirmed against a live HTTP 200 from `api.sam.gov` — no key exists on the build machine and the API is geo-blocked from it. **After the first keyed live run, record which mode the log reported here**, together with the run date and the region it ran from.
- **Retries:** every request retries with exponential backoff on 429 and 5xx. If your daily quota runs out mid-run, the Actor delivers what it already has, records a warning in `SUMMARY`, and exits cleanly instead of failing.

### Use cases

- **Federal bid pipeline** — schedule a daily run filtered to your NAICS codes and set-aside eligibility, and push new solicitations straight into HubSpot, Salesforce or a Slack channel the morning they post.
- **Capture and teaming research** — pull Sources Sought and Presolicitation notices to see what an agency intends to buy months before the RFP, with the contracting officer's email already in the row.
- **Competitive and pricing intelligence** — collect Award Notices for your NAICS or PSC codes to track who is winning, at what dollar value, for which agency and where.

### Support

Found a bug, a field that should be added, or a filter that behaves oddly? Open an issue on this Actor's **Issues** tab on Apify Store, including your input JSON and the run ID. Issues are answered within 14 days.

# Actor input Schema

## `apiKey` (type: `string`):

Required. Your own free SAM.gov public API key. Sign in at sam.gov, open Account Details and click 'Request API Key' (personal, non-federal keys allow 10 requests per day; a federal or entity-linked account gets 1,000). The key is stored encrypted by Apify and never written to the log. If you leave this empty, the Actor falls back to the SAM\_API\_KEY environment variable.

## `daysBack` (type: `integer`):

How many days of postings to fetch, counting back from today. Ignored when both 'Posted from' and 'Posted to' are set. SAM.gov allows a window of at most one year.

## `dateFrom` (type: `string`):

Earliest posted date, for example 2026-09-01. Leave empty to use 'Days back'.

## `dateTo` (type: `string`):

Latest posted date, for example 2026-09-07. Leave empty for today.

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

Only return these notice types (SAM.gov 'ptype' codes). Leave empty for every type. You may also type full names such as 'Solicitation' or 'Sources Sought'.

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

Case-insensitive OR match over title, solicitation number, agency, NAICS, classification code, set-aside and awardee. A single keyword is sent to the SAM.gov API as a title search; two or more are matched locally.

## `title` (type: `string`):

Server-side SAM.gov title search. Takes precedence over 'Keywords' for the API query; keywords are then applied locally as an extra filter.

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

Industry codes, for example 541512. One full 6-digit code is sent to the API; several codes (or prefixes such as 5415) are matched locally against every NAICS code on the notice.

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

SAM.gov set-aside codes, for example SBA (total small business), SBP, 8A, 8AN, HZC, HZS, SDVOSBC, SDVOSBS, WOSB, EDWOSB, VSA, VSS. One code is sent to the API; several are matched locally.

## `classificationCode` (type: `string`):

Product or Service Code, for example D310 or Z2AA. Sent to the SAM.gov API as 'ccode'.

## `state` (type: `string`):

Two-letter US state code, for example TX. Sent to the SAM.gov API.

## `zip` (type: `string`):

ZIP code of the place of performance, for example 35806. Sent to the SAM.gov API.

## `organizationName` (type: `string`):

Contracting organization name as SAM.gov spells it, for example 'ACC-APG HUNTSVILLE'. Sent to the SAM.gov API.

## `solicitationNumber` (type: `string`):

Fetch a single solicitation by its number, for example W912DY26R0042.

## `noticeId` (type: `string`):

Fetch a single notice by its SAM.gov noticeId (the hex id in the sam.gov/opp/... URL).

## `responseDeadlineFrom` (type: `string`):

Only notices whose response deadline falls on or after this date. Useful to skip notices that already closed.

## `responseDeadlineTo` (type: `string`):

Only notices whose response deadline falls on or before this date.

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

Drop notices SAM.gov has archived (active = No). Applied locally after fetching.

## `fetchDescriptions` (type: `boolean`):

Also download the full notice text into the 'description' field. WARNING: this costs one extra SAM.gov API request per record and personal non-federal keys allow only 10 requests per day, so use it with a small 'Max results'.

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

Stop after this many opportunities. You are charged per result returned, so this is also your cost cap.

## Actor input object example

```json
{
  "daysBack": 7,
  "noticeTypes": [
    "o",
    "k",
    "p",
    "r"
  ],
  "keywords": [],
  "naicsCodes": [],
  "setAsides": [],
  "activeOnly": false,
  "fetchDescriptions": false,
  "maxItems": 100
}
```

# Actor output Schema

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

All opportunities returned by the run, one JSON object each. Append ?format=csv or ?format=xlsx to download a spreadsheet.

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

Counts, the date window used, source freshness and any per-source warnings, written at the end of the run.

# 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 = {
    "daysBack": 7,
    "noticeTypes": [
        "o",
        "k",
        "p",
        "r"
    ],
    "keywords": [],
    "naicsCodes": [],
    "setAsides": [],
    "activeOnly": false,
    "fetchDescriptions": false,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("knotty_mistveil/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 = {
    "daysBack": 7,
    "noticeTypes": [
        "o",
        "k",
        "p",
        "r",
    ],
    "keywords": [],
    "naicsCodes": [],
    "setAsides": [],
    "activeOnly": False,
    "fetchDescriptions": False,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("knotty_mistveil/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 '{
  "daysBack": 7,
  "noticeTypes": [
    "o",
    "k",
    "p",
    "r"
  ],
  "keywords": [],
  "naicsCodes": [],
  "setAsides": [],
  "activeOnly": false,
  "fetchDescriptions": false,
  "maxItems": 100
}' |
apify call knotty_mistveil/sam-gov-opportunities --silent --output-dataset

```

## MCP server setup

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