# SAM.gov Contract Opportunities, Incumbents & Grants (`signalscouter/us-federal-contracts-signals`) Actor

New US federal contract notices from SAM.gov filtered by keyword, NAICS, PSC, agency, set-aside and type, with optional USAspending incumbent lookup and Grants.gov opportunities. No API key needed.

- **URL**: https://apify.com/signalscouter/us-federal-contracts-signals.md
- **Developed by:** [Signal Scout](https://apify.com/signalscouter) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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, Incumbents & Grants

Get **new US federal contract opportunities from SAM.gov** as clean rows, filtered by keyword, NAICS, PSC, agency, set-aside, notice type, state and posting date. Optionally add a **USAspending incumbent and competitor lookup** for each notice and **Grants.gov grant opportunities** in the same run. No API key, account or login is needed.

Built by **signalscouter**. Questions or problems: signalscouter.data@gmail.com, or open an issue on this Actor.

### What you get

| Row type | What's in it | Event |
|---|---|---|
| `opportunity` | Notice ID, title, solicitation number, notice type, posted date, response deadline (as published and in UTC), department / sub-tier / office (with CGAC, FPDS and AAC codes), NAICS, PSC, set-aside, place of performance, award details on award notices, description (truncated to your limit), link to SAM.gov, matched keywords | `opportunity-result` |
| `opportunity.incumbents` (optional) | Top 5 recipients and 5 largest recent prime contracts from the **same awarding agency** for the **same NAICS / PSC** over the last N years, with USAspending links | `incumbent-enrichment`, only when something was found |
| `grant` (optional) | Grants.gov posted or forecasted opportunities: number, title, agency, open and close dates, award ceiling and floor, estimated total funding, expected number of awards, cost sharing, funding instruments and categories, eligible applicants, description, link | `grant-result` |
| `error` | Anything that couldn't be processed (bad input value, source unavailable, a grant whose details failed) | **never charged** |

Switch views in the dataset: **Opportunities**, **Incumbents**, **Award notices**, **Grants**, **Contacts (opt-in)**, **Errors**. Each run also writes a `RUN_SUMMARY` record with rows read, how many each filter removed, rows delivered and charged, cache status and request counts.

### Typical uses

- A daily alert for new solicitations in your NAICS codes, with **Only new since last run** turned on and a schedule.
- A set-aside pipeline, for example SDVOSB or 8(a) notices for one agency.
- Capture research: for each notice, which companies already win that agency's work in that code (likely incumbents and competitors).
- Grants and contracts in one feed for research organisations and small businesses.

### Input example

```json
{
  "keywords": ["cybersecurity", "zero trust"],
  "naicsCodes": ["5415"],
  "setAsides": ["SBA", "SDVOSBC"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Sources Sought"],
  "postedSince": "7 days",
  "maxResults": 200,
  "includeIncumbents": true,
  "includeGrants": true,
  "maxGrants": 50,
  "onlyNew": true,
  "monitorId": "cyber-daily"
}
```

Filters combine with AND. Values inside one filter combine with OR. Keywords match whole words or phrases in the title, solicitation number and description, case-insensitive. NAICS and PSC accept prefixes (`54` matches every 54xxxx code). Agencies accept partial names (`Veterans Affairs`, `Army Corps`) or CGAC / FPDS / AAC codes. Codes only work for SAM.gov notices, not for grants. The state filter uses the place-of-performance state, and the contracting office's state only when a notice gives no place of performance. Notices past their response deadline are hidden by default. Award notices have no deadline, so they're kept.

### Where the data comes from

| Source | How it's used | Terms |
|---|---|---|
| **SAM.gov Contract Opportunities public data extract** (`ContractOpportunitiesFullCSV`, refreshed once a day) | Downloaded once per run as a stream and filtered on the fly | Public data published by GSA for bulk download. No key needed. |
| **USAspending.gov API** (`spending_by_category/recipient`, `spending_by_award`, agency references) | Incumbent lookup, only when enabled | Public API, no key. Requests are rate-limited to 4 per second, and lookups are cached within a run. |
| **Grants.gov API** (`search2`, `fetchOpportunity`) | Grant opportunities, only when enabled | Public API, no key. Requests are rate-limited to 2 per second. |

This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.

Every request identifies itself as `signalscouter-us-federal-contracts/1.0 (+signalscouter.data@gmail.com)`. The Actor uses no browser and no proxies, and it doesn't work around any access controls. It isn't affiliated with or endorsed by GSA, SAM.gov or the US Treasury (USAspending). Check the official notice on SAM.gov before you bid, because only the official notice and its attachments are authoritative.

### Important limits (please read)

- **Freshness.** SAM.gov publishes the extract once a day (around 03:30 UTC on recent days), so notices posted later that day appear in the next day's file. This Actor isn't a real-time feed.
- **Active notices only.** The extract contains active notices. Archived and cancelled notices drop out of it.
- **Incumbents are an estimate, not a confirmed predecessor contract.** The lookup returns recent prime contract awards from the same awarding agency (sub-tier where possible) for the same NAICS and/or 4-character PSC. That is a strong pointer to who currently does this kind of work for the agency, but it isn't proof of who holds the contract being re-competed. The lookback (`incumbentLookbackYears`, default 3) is capped at 4 years and never reaches awards dated before 4 April 2022, because recipient names on older awards are Dun & Bradstreet-restricted data. `incumbents.basis` shows exactly what was matched. Army, Navy and Air Force notices are matched under the Department of Defense, as USAspending records them. The enrichment is charged only when `status` is `ok`. These statuses aren't charged: `noMatches` (no past awards found), `insufficientCodes` (the notice has no NAICS and no 4-character PSC), `agencyNotFound` (the agency doesn't report contracts to USAspending, for example USPS; we don't fall back to a government-wide guess) and `failed` (USAspending didn't answer after retries). If USAspending fails 5 lookups in a row, any retries still waiting and the rest of that run's lookups are skipped straight away as `failed` (`USASPENDING_CIRCUIT_OPEN`, not charged), so an outage doesn't stall the run. This can happen inside the first batch of lookups. Each request times out after 30 seconds.
- **Attachments aren't downloaded.** Use `samUrl` to open the notice and its documents.
- **Descriptions** are cut to `descriptionMaxChars` (default 1,500). `descriptionTruncated` tells you when that happened.
- **Contacts are off by default.** Contracting officers' and grant officers' names, emails and phones are published by the government for questions about a notice. If you turn on `includeContacts`, use them for that purpose and follow anti-spam and privacy law.
- **Grants.gov matching.** Keywords are sent to the Grants.gov search, and the agency filter is applied to the agency name and the Grants.gov agency code (for example `NSF` or `HHS-NIH11`). SAM.gov CGAC, FPDS and AAC codes (such as `097`) don't exist in Grants.gov, so a code-only agency filter matches no grants. Use a name such as `Defense` if you want both. NAICS, PSC, set-aside and state filters don't apply to grants. Grants.gov requests time out after 30 seconds. If 5 grant detail requests in a row fail after retries, the remaining grants are skipped. You get one `GRANTS_CIRCUIT_OPEN` error row (not charged), and the skipped grants aren't marked as seen, so a later run picks them up.

### Cost and speed

The SAM.gov file is about 200 MB and is streamed in a single pass straight from SAM.gov's download service, so memory stays low (under 100 MB in our test runs) however many notices you ask for. Reading and filtering the whole file takes about 3 seconds on Apify. A 1,000-notice run took about 10 seconds in total, and each incumbent lookup adds about 0.3 seconds. The optional **Keep a copy of today's SAM.gov file between runs** setting is off by default. In our tests, re-downloading was faster and cheaper than reading a stored copy, so turn it on only if you run many times a day and want SAM.gov downloaded at most once per day.

#### How "Only new since last run" works

- Delivered notice and grant IDs are remembered for 400 days in a store named `us-federal-contracts-signals-state` in your own account, with one record per `monitorId`.
- Progress is saved during the run, not only at the end. Before each batch is delivered, its IDs are written to a small journal record in the same store. In the last 90 seconds before the run's timeout, the full state is also saved after every batch. If a run times out or crashes, the next run of the same monitor merges those records, so nothing is delivered or charged twice. Each attempt of a run writes its own journal records, so a restarted run can't overwrite them. If Apify restarts or resurrects a run, the run first reads its own dataset and skips everything already in it, so nothing is pushed or charged again. Max opportunities and Max grants count across all attempts of the run. This applies whether or not "Only new since last run" is on. In the worst case, the batch being written at the exact moment of the crash (up to 100 notices, 8 notices with incumbents, or 4 grants) is skipped rather than delivered again.
- **Notices beyond Max opportunities (and grants beyond Max grants) are not marked as seen.** They stay eligible, newest first, for the next run. If a monitor regularly finds more new items than your limit, it works through the backlog over several runs. Raise the limit if you want everything in one run.
- Items skipped because of your maximum charge per run aren't marked as seen either, so the next run delivers them.

### Pricing

Pay per event. You only pay for results delivered.

| Event | Price | When |
|---|---|---|
| Opportunity result | **$0.002** ($2.00 per 1,000) | Each SAM.gov contract notice saved to the dataset |
| Incumbent enrichment | **$0.005** ($5.00 per 1,000) | Only when the USAspending lookup found past awards (`incumbents.status` is `ok`), at most once per notice |
| Grant result | **$0.002** ($2.00 per 1,000) | Each Grants.gov opportunity saved to the dataset |

**Not charged:**

- Error rows.
- Notices removed by your filters or cut by `maxResults` / `maxGrants`.
- Notices skipped by **Only new since last run**.
- Enrichments with status `noMatches`, `insufficientCodes`, `agencyNotFound` or `failed`.
- The Actor start. There's no start fee.

If you set a **maximum charge per run**, the Actor stops cleanly before it would go over that amount. Examples:

- A daily alert that finds 50 new notices costs $0.10.
- 200 notices with incumbent lookup cost at most $1.40.
- 100 grants cost $0.20.

#### Scheduling tip: once a day is plenty

SAM.gov publishes its public Contract Opportunities file **once a day** (around 03:30 UTC on recent days), so running more often than daily won't find new notices. They will be in the next day's file. Grants.gov changes during the day, but slowly. For monitoring we suggest:

- A daily schedule, for example 06:00 UTC, with **Only new since last run** on and a `monitorId` for each set of filters.
- If a run finds nothing new, you pay nothing, because there are no result events and no start fee. Extra runs won't get you more data, though, so daily is the sensible rhythm.

### Output example (trimmed)

```json
{
  "recordType": "opportunity",
  "noticeId": "0b1c…",
  "title": "Zero Trust Architecture Support Services",
  "noticeType": "Combined Synopsis/Solicitation",
  "postedDate": "2026-10-19",
  "responseDeadlineUtc": "2026-11-03T21:00:00+00:00",
  "department": "DEPT OF DEFENSE",
  "subTier": "DEFENSE HEALTH AGENCY (DHA)",
  "naicsCode": "541512",
  "pscCode": "DA01",
  "setAsideCode": "SBA",
  "samUrl": "https://sam.gov/opp/0b1c…/view",
  "incumbents": {
    "status": "ok",
    "basis": {"awardingAgency": "Department of Defense", "awardingSubAgency": "Defense Health Agency",
              "agencyResolvedBy": "officeCode", "naicsCode": "541512", "pscCode": "DA01",
              "from": "2023-10-20", "to": "2026-10-20"},
    "topRecipients": [{"name": "…", "uei": "…", "obligatedUsdInWindow": 12345678.9, "usaspendingUrl": "https://www.usaspending.gov/recipient/…/latest"}],
    "largestRecentAwards": [{"awardId": "…", "recipientName": "…", "awardAmountUsd": 5000000.0, "endDate": "2027-01-31", "usaspendingUrl": "https://www.usaspending.gov/award/…"}],
    "note": "Recent prime contract awards for the same agency and codes (market incumbents), not a confirmed predecessor contract of this notice."
  }
}
```

### FAQ

**Do I need a SAM.gov API key?** No. The Actor reads the public daily extract, not the rate-limited SAM.gov API.

**Can I get historical or archived notices?** No. Only active notices in today's extract are included. You can use `postedSince` to go back as far as the active notices go.

**Why is `responseDeadlineUtc` sometimes slightly off?** Some notices publish a deadline without a time zone. Those are read as US Eastern (UTC−5), and date-only deadlines are read as 23:59 that day.

**Is it legal?** All three sources publish public government data for reuse. Respect each site's terms and use contact details responsibly.

### More signalscouter Actors

| Actor | What it gives you |
|---|---|
| [SEC Form D Funding Rounds & Startup Signals](https://apify.com/signalscouter/sec-form-d-funding-signals) | Daily SEC Form D filings: who raised money, how much, industry, state and investors |
| [Career Site Jobs Scraper](https://apify.com/signalscouter/career-site-jobs-scraper) | Jobs from 12 ATSes (Greenhouse, Lever, Ashby, Workday, Workable and more) in one schema, from company domains |
| [Workday Jobs Scraper](https://apify.com/signalscouter/workday-jobs-scraper) | Jobs from any `myworkdayjobs.com` career site, including sites with more than 2,000 jobs |
| [UK Public Sector Jobs Scraper](https://apify.com/signalscouter/uk-public-sector-jobs-scraper) | NHS Jobs, Teaching Vacancies and DWP Find a Job |
| [Company Tech Stack & Domain Signals](https://apify.com/signalscouter/company-tech-stack-signals) | What a company's website runs on, plus DNS, email security and domain age |

# Actor input Schema

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

Match any of these words or phrases in the notice title, solicitation number or description (whole words, case-insensitive). Leave empty to match everything that passes the other filters. Also used as Grants.gov search terms.

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

NAICS codes or prefixes, 2-6 digits (e.g. 5415 matches 541511, 541512...).

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

Product Service Codes or prefixes, 1-4 characters (e.g. D3 or R425). Incumbent lookup uses the full 4-character PSC.

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

Department, sub-tier or office names (partial match, e.g. 'Veterans Affairs', 'Army Corps') or CGAC / FPDS / AAC codes. Also filters grants by agency name or Grants.gov agency code (e.g. NSF, HHS-NIH11); SAM.gov CGAC/FPDS/AAC codes don't match grants.

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

Only notices with one of these set-aside types.

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

Only these SAM.gov notice types. Empty = all types.

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

Two-letter US state codes (e.g. VA, TX). Matches the place of performance, or the contracting office's state when no place is given.

## `postedSince` (type: `string`):

Only notices posted on or after this date (e.g. 2026-10-01) or within a relative period (e.g. '7 days', '2 weeks').

## `hideExpired` (type: `boolean`):

Skip notices whose response deadline has already passed. Award notices have no deadline and are kept.

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

Newest matching notices to return. 0 = no limit (can be tens of thousands with broad filters). With 'Only new since last run', matching notices beyond this limit aren't marked as seen, so later runs deliver them.

## `includeIncumbents` (type: `boolean`):

For each notice, look up the top recipients and largest recent prime contracts awarded by the same agency for the same NAICS/PSC on USAspending.gov. These are likely incumbents and competitors, not a confirmed predecessor contract. Charged as an extra event only when results are found.

## `incumbentLookbackYears` (type: `integer`):

How many years of past awards to search (at most 4: older USAspending recipient names are D\&B-restricted).

## `includeGrants` (type: `boolean`):

Add posted and forecasted federal grant opportunities from Grants.gov, matched by your keywords, agencies and posted-since date.

## `maxGrants` (type: `integer`):

Newest grant opportunities to return. 0 = up to 5000. With 'Only new since last run', grants beyond this limit stay eligible for later runs.

## `includeOpportunities` (type: `boolean`):

Turn off to get grants only.

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

Add the published point-of-contact names, emails and phones. Off by default; if you turn it on, follow privacy and anti-spam laws when you use them.

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

Truncate notice and grant descriptions to this many characters (0 = no description).

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

Skip notices and grants this monitor already returned in an earlier run. Use with a schedule to get only fresh items. Progress is saved during the run, and items delivered by a run that timed out or crashed are recognised by the next run, so they aren't delivered or charged twice.

## `monitorId` (type: `string`):

Name for the onlyNew memory, so several schedules with different filters don't share it.

## `useCache` (type: `boolean`):

Off (recommended): every run streams the daily SAM.gov file directly, which we measured as faster and cheaper on Apify. On: a compressed copy (~45 MB) is kept in your own named key-value store and reused until SAM.gov publishes a new file, so SAM.gov is downloaded at most once a day. Adds a small storage-transfer cost per run.

## Actor input object example

```json
{
  "keywords": [
    "cybersecurity"
  ],
  "postedSince": "7 days",
  "hideExpired": true,
  "maxResults": 200,
  "includeIncumbents": false,
  "incumbentLookbackYears": 3,
  "includeGrants": false,
  "maxGrants": 100,
  "includeOpportunities": true,
  "includeContacts": false,
  "descriptionMaxChars": 1500,
  "onlyNew": false,
  "monitorId": "default",
  "useCache": false
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per SAM.gov contract notice.

## `incumbents` (type: `string`):

Notices with their USAspending incumbent/competitor lookup (when enabled).

## `awards` (type: `string`):

Award notices with awardee and amount.

## `grants` (type: `string`):

Grants.gov opportunities (when enabled).

## `contacts` (type: `string`):

Published points of contact (only when includeContacts is on).

## `errors` (type: `string`):

Rows that could not be processed (never charged).

## `runSummary` (type: `string`):

RUN_SUMMARY: rows read, matched, filtered, delivered, charged, errors, cache status and request counts.

# 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": [
        "cybersecurity"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalscouter/us-federal-contracts-signals").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": ["cybersecurity"] }

# Run the Actor and wait for it to finish
run = client.actor("signalscouter/us-federal-contracts-signals").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": [
    "cybersecurity"
  ]
}' |
apify call signalscouter/us-federal-contracts-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,signalscouter/us-federal-contracts-signals"
        }
    }
}
```

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/pd7OYOe6pSLrLDFYL/builds/kFrlIb1FYPVtggElj/openapi.json
