# Federal Opportunity Feed (`publicrecords/govcon-opportunity-feed`) Actor

Federal contract and grant opportunities from SAM.gov and Grants.gov in one feed — duplicates removed, agency names cleaned up, who won similar work before, and what changed since you last checked. No SAM.gov key.

- **URL**: https://apify.com/publicrecords/govcon-opportunity-feed.md
- **Developed by:** [Public Records](https://apify.com/publicrecords) (community)
- **Categories:** Lead generation, AI, Developer tools
- **Stats:** 3 total users, 2 monthly users, 25.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 opportunity records

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

### What does GovCon Opportunity Feed do?

**GovCon Opportunity Feed** returns every US federal **contract and grant opportunity** matching your NAICS codes, agencies, keywords, set-asides, and states — across **SAM.gov** and **Grants.gov** — as **one deduplicated, agency-normalized schema**. In `enrich` mode it adds **who won similar work** (USASpending, last 36 months, matched by UEI). In `monitor` mode it returns **only what changed** since your last run: new, updated, deadline moved, awarded, cancelled.

Built for capture managers, GovCon BD teams, proposal shops, GovCon SaaS builders, and AI agents that triage opportunities.

> This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services. Not affiliated with GSA, SAM.gov, Grants.gov, or the U.S. Department of the Treasury.

#### Why this Actor?

- 🔗 **Two sources, one schema** — SAM.gov contracts and Grants.gov grants in the same fields, with a canonical agency table across SAM, Grants.gov, and USASpending codes
- 🧬 **Lineage-resolved** — presolicitation → solicitation → award collapse to one `opportunityKey`; no duplicates across stages
- 🔔 **Monitor mode** — deltas only (`new`, `updated`, `deadline_moved`, `awarded`, `cancelled`, `removed`), so a daily schedule costs cents
- 🏆 **Category leaders** — top recipients by agency × NAICS from USASpending, matched by UEI, with award counts and obligations
- 🧾 **Provenance on every row** — `sourceUrl`, `retrievedAt`, `sourceVersion`, `recordHash`
- 📡 **Official CSV + APIs only** — daily SAM Data Services extract by default; no HTML scraping, no login-gated data, no personal data
- 💵 **Error and status rows are never charged**

#### Modes

| Mode | Returns | Charged per |
|---|---|---|
| `snapshot` | all matching opportunities | opportunity |
| `monitor` | only changes since your last run with the same filters | delta (+ opportunity for new) |
| `enrich` | snapshot + category leaders (agency×NAICS peers) per opportunity | opportunity + category-leader |

#### No SAM.gov key required (default)

**Contracts come from GSA’s public daily Data Services CSV** (`samBulk=true` by default) — no API key, no daily request quota. Grants.gov and USASpending also need no key. Monitor deltas are **CSV-diff** against a saved KV snapshot (hashes / deadlines / notice types), not the Search API.

**Optional** free SAM public API key (`samApiKey`) + `samEnrichLimit` adds **attachment URLs** and fuller descriptions only. Sign in at sam.gov → Account Details → Request API Key. Stored encrypted; never logged.

### Default mode = monitor (delta)

Default `mode` is **`monitor`** (change deltas since last run with the same filters). Quiet days are near-**$0** for the buyer (actor-start only; delta PPE only when something changes). Full **`snapshot`** is opt-in and can approach ~$10 at default caps — do not use snapshot as the scheduled default.

### Input

| Field | Default | Description |
|---|---|---|
| `mode` | `monitor` | monitor (default) / snapshot / enrich |
| `sources` | `["sam","grants"]` | |
| `samApiKey` | — | optional; attachments + full descriptions only |
| `samBulk` | `true` | daily CSV path (recommended) |
| `samEnrichLimit` | `5` | max optional SAM API enrich calls/run |
| `naics` | `[]` | 6-digit or prefix |
| `agencies` | `[]` | names or codes (DoD, DHS, EPA…) |
| `keywords` | `[]` | |
| `setAside` | `[]` | SB, 8A, HUBZone, SDVOSB, WOSB, NONE |
| `states` | `[]` | place of performance |
| `noticeTypes` | all | presolicitation, sources\_sought, solicitation, combined\_synopsis, award\_notice, special\_notice, grant\_forecast, grant\_posted |
| `postedWithinDays` | `14` | Declared product window (14 days). |
| `deadlineAfter` | today | excludes expired |
| `includeDescriptions` | `false` | |
| `includeContacts` | `false` | official contacts only |
| `enrichTopN` | `5` | |
| `maxItems` | `10000` | Default = **10,000 most recently posted** of the 14d window. Raise to get the rest (schema max **50000**; PPE for what you take). Cap after sort postedDate desc → Sol#. |

#### Example: daily monitor for DoD/DHS IT work

```json
{ "mode": "monitor", "naics": ["5415"], "agencies": ["DoD", "DHS"], "setAside": ["SB", "SDVOSB"], "samBulk": true, "samEnrichLimit": 0 }
```

### Sample output (opportunity)

```json
{
  "recordType": "opportunity",
  "opportunityKey": "sam:sol:W912DQ26R0012",
  "sources": ["sam"],
  "kind": "contract",
  "noticeType": "solicitation",
  "title": "IT Support Services",
  "agency": { "canonicalName": "Department of Defense", "canonicalCode": "097", "subAgency": "Department of the Army", "raw": "DEPT OF DEFENSE" },
  "naics": ["541512"],
  "setAside": "SB",
  "placeOfPerformance": { "state": "TX", "country": "USA" },
  "postedAt": "2026-09-01T00:00:00.000Z",
  "deadlineAt": "2026-10-01T00:00:00.000Z",
  "url": "https://sam.gov/opp/…/view",
  "retrievedAt": "2026-09-16T12:00:00.000Z",
  "recordHash": "…"
}
```

### Pricing

| Event | Price |
|---|---|
| Actor start | ~$0.00005 |
| Opportunity record | **$0.002** |
| Delta record (monitor) | **$0.02** |
| Category-leader record (enrich; PPE event id `incumbent-record`) | **$0.01** |
| Status / error rows | **free** |

Snapshot of 200 opportunities ≈ $0.40 · daily monitor with ~15 changes ≈ $0.30/day · enrich 50 opportunities × 5 category leaders ≈ $2.60.

### Snapshot completeness (option D)

Declared default window is **`postedWithinDays=14`**. Snapshot/monitor **materialize the full filter-matched CSV set**, then **sort by `postedAt` desc → Sol# (normalized)** and apply `maxItems`.

**Default return:** the **10,000 most recently posted** opportunities in that **14-day** window (Active + filters).

**`postedAt` / window semantics (0.1.21+):** clusters resolve on the **full daily CSV** (Active **or** Inactive matching filters). Per opportunity: `postedAt` = MIN `PostedDate` over **all** notices in the file-level cluster; `lastActivityAt` = MAX; `samNoticeIds` = union. **Active applies only in the keep rule:** keep opportunities with **any Active=Yes notice** **and** `lastActivityAt` within `postedWithinDays` (default 14). `postedAt` moves only when SAM removes that earliest notice from the file — not when it ages out of the 14d window or flips Inactive. Snapshot sort stays `postedAt` desc. Hashed fields come from the **stage winner** (highest STAGE\_ORDER; same-stage → newer `updatedAt`/`postedAt`; equal → **NoticeId ascending**) — Active/window-independent. **`postedAt` drift alone does not emit `updated`** — `recordHash` excludes `postedAt`/`lastActivityAt`. Every run reports **`CAP_COVERAGE.covered/total`**.

**Raising `maxItems`** (schema max **50000**) returns the rest of the matched window (you pay PPE for what you take). A cap always drops the **oldest** rows — deterministic covered set, not CSV physical order. When `afterFilters > maxItems`, full-set dedup/delta claims are not PASS.

### Limitations

- SAM.gov results depend on your key's daily quota; exhaustion is reported, not hidden
- Grants.gov has no NAICS; grants are matched by agency/keyword only
- Category-leader matching is by UEI; pre-UEI awards are excluded
- `cancelled` is emitted only when SAM Type confirms cancellation (e.g. Modification/Amendment/Cancel); otherwise absence is `removed` with reason `archived` | `deadline_passed` | `unknown`
- No entity/registration data (D\&B restrictions); no scraping of sam.gov pages

### Candidate builds / GIT\_SHA

Every Apify build is cut from a tagged commit. The image receives `GIT_SHA` as a Docker build-arg (also `ENV GIT_SHA`) and echoes it on `RUN_SUMMARY`. See `docs/BUILD_NOTES.md` for the candidate `0.1.26` SHA. Store tip / `latest` stays on **0.1.25** until Mark promotes after one clean scheduled night on the candidate tag.

### Legal

SAM.gov opportunity data: open US government data, used via the official API under https://sam.gov/about/terms-of-use. Grants.gov: https://grants.gov/api/terms-conditions. USASpending: https://www.usaspending.gov/about (D\&B limits apply; this feed is not a D\&B substitute). See TERMS.md.

# Actor input Schema

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

monitor (default): only what changed since your last run with these filters — near-$0 on a quiet day; delta PPE only when something changes. snapshot: full matching set now (opt-in; can be ~$10 at default caps). enrich: snapshot plus category leaders. rehashState: rebuild monitor state hashes without emitting deltas (ops / hash migration; no PPE deltas).

## `sources` (type: `array`):

Allowed values: sam, grants. SAM defaults to daily Data Services CSV (samBulk=true, no key). Grants.gov needs no key.

## `samApiKey` (type: `string`):

Optional when samBulk=true. Required only for API enrich (resourceLinks) / monitor checks / samBulk=false. Personal public API key from sam.gov (Account Details → Request API Key). Free. Stored encrypted; never logged.

## `naics` (type: `array`):

6-digit or prefix, e.g. 541512 or 5415.

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

Names or codes, e.g. Department of Defense, DHS, EPA.

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

Free-text keywords matched against opportunity titles.

## `setAside` (type: `array`):

SB, 8A, HUBZone, SDVOSB, WOSB, NONE

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

Two-letter US state codes for place of performance (contracts).

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

Filter by notice type, e.g. solicitation, award\_notice, grant\_posted.

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

Only include opportunities posted within the last N days. Declared product default is **14 days** (Thursday product window). Schedules and Store listing align to 14d; raise if you need a longer lookback (PPE scales with matches).

## `deadlineAfter` (type: `string`):

Defaults to today; excludes expired opportunities.

## `includeDescriptions` (type: `boolean`):

When true, fetch full grant synopses (slower).

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

When true, include SAM point-of-contact fields.

## `enrichTopN` (type: `integer`):

Category leaders (or predecessors) per opportunity in enrich mode.

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

Hard cap on opportunity rows returned (and billed) after sort by postedDate desc → Sol#. Default 10000. Schema maximum 50000 so a buyer can opt into covering a full Active+14d/30d window (they pay PPE for what they take). Under the default 14d window, Active matches often exceed 10k — runs emit CAP\_TRUNCATED + CAP\_COVERAGE.covered/total (never silent). Cap always drops the oldest rows.

## `samBulk` (type: `boolean`):

When true (default), download GSA's public daily Contract Opportunities CSV — no API quota. API key only needed for optional enrich (resourceLinks) and monitor checks within samEnrichLimit.

## `samEnrichLimit` (type: `integer`):

Max SAM Opportunities API calls per run for resourceLinks/description enrich and selective monitor checks. Keep ≤5–10 on a personal key (10/day with no Role).

## `incumbentMode` (type: `string`):

category\_leaders (default): top USASpending recipients by agency×NAICS. predecessor: true prior-awardee/recompete join (stub until PREDECESSOR\_JOIN.md ships). Proof schedules must keep default.

## Actor input object example

```json
{
  "mode": "snapshot",
  "sources": [
    "sam"
  ],
  "naics": [
    "541512"
  ],
  "agencies": [],
  "keywords": [],
  "setAside": [],
  "states": [],
  "noticeTypes": [],
  "postedWithinDays": 14,
  "includeDescriptions": false,
  "includeContacts": false,
  "enrichTopN": 5,
  "maxItems": 25,
  "samBulk": true,
  "samEnrichLimit": 5,
  "incumbentMode": "category_leaders"
}
```

# Actor output Schema

## `results` (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 = {
    "mode": "snapshot",
    "sources": [
        "sam"
    ],
    "naics": [
        "541512"
    ],
    "postedWithinDays": 14,
    "includeDescriptions": false,
    "includeContacts": false,
    "maxItems": 25,
    "samBulk": true,
    "samEnrichLimit": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("publicrecords/govcon-opportunity-feed").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 = {
    "mode": "snapshot",
    "sources": ["sam"],
    "naics": ["541512"],
    "postedWithinDays": 14,
    "includeDescriptions": False,
    "includeContacts": False,
    "maxItems": 25,
    "samBulk": True,
    "samEnrichLimit": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("publicrecords/govcon-opportunity-feed").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 '{
  "mode": "snapshot",
  "sources": [
    "sam"
  ],
  "naics": [
    "541512"
  ],
  "postedWithinDays": 14,
  "includeDescriptions": false,
  "includeContacts": false,
  "maxItems": 25,
  "samBulk": true,
  "samEnrichLimit": 0
}' |
apify call publicrecords/govcon-opportunity-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,publicrecords/govcon-opportunity-feed"
        }
    }
}
```

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/lSCmJXC8EcsrtKezu/builds/ieiJkjXZTS5KSTMgb/openapi.json
