# SAM.gov Contracts & Exclusions API - No Scraping, No Start Fee (`mooseandraven/samgov-federal-contracting-suite`) Actor

Federal contract opportunities + exclusions via the official api.sam.gov REST API - not fragile site-scraping. FAR 52.209-6 batch subcontractor screening included (rare). No per-run start fee (others charge $0.10/run). Honest coverage disclosures - never a silent partial result.

- **URL**: https://apify.com/mooseandraven/samgov-federal-contracting-suite.md
- **Developed by:** [Moose & Raven](https://apify.com/mooseandraven) (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 $4.00 / 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.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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 & Exclusions API (JSON)

**Who this is for:** GovCon business-development teams hunting new NAICS-matched contract
opportunities, and prime contractors who have to clear every subcontractor against SAM.gov
exclusions before award. This actor delivers both jobs from one normalized dataset - get alerted
to new opportunities that match your NAICS codes, and screen your whole subcontractor list
against exclusions before you sign, in one actor, one schema, one SAM.gov API key.

**Worked example:** Set `naicsCodes: ["541511"]` with a `postedFrom`/`postedTo` window and the
actor returns `opportunity` records for newly posted solicitations in that NAICS code - real
fields like `title`, `noticeType`, `responseDeadline`, `setAsideType`, and `uiLink` (the
human-readable SAM.gov page you'd hand to a bid team). Before awarding a subcontract, submit
`batchScreenList: [{ "entityName": "Acme Corp" }]` and get back one `batch-screen-result` per
entry: `matched: false` means clear to proceed, `matched: true` returns the full matched
`exclusion` record (excluding agency, exclusion type, dates) attached - so you can satisfy the
FAR 52.209-6 written-notification duty before you sign, without a second tool or a manual SAM.gov
lookup.

Search federal contract opportunities and screen against the SAM.gov Exclusions (debarment/
suspension) list - in one normalized, NAICS-filterable dataset, via the official `api.sam.gov`
REST API (bring your own API key). No scraping of SAM.gov's website. Optional NAICS-filtered
incremental monitoring keeps scheduled runs pulling only what's new, and a batch-screening mode
checks a whole vendor/subcontractor list against exclusions in one run.

### 1. What makes this different from other SAM.gov actors

We checked the leading SAM.gov actor in the Apify Store (154 users, 1.0-star rating from its own
reviewers) directly: its own published documentation states it hits "the SAM.gov frontend search
API" - an undocumented internal endpoint, not the official `api.sam.gov`. **SAM.gov's Terms of Use
explicitly prohibit exactly that**: "systematic access (electronic harvesting)... of the website"
is banned, and a detected violation gets the associated account denied SAM.gov access outright -
a real, standing risk for anyone relying on it, not just a theoretical ToS technicality. This actor
calls the official, sanctioned `api.sam.gov` REST API exclusively. It does not touch SAM.gov's
website, frontend or otherwise.

Three concrete differences from that competitor, not just "we're more careful":

- **No per-run start fee.** That competitor charges $0.10 the moment any run starts, before a
  single record is returned - a real cost on every test run or near-empty query. This actor
  charges only for records actually delivered; a run that finds nothing costs nothing.
- **FAR 52.209-6 batch subcontractor screening.** Submit a vendor list, get a matched/clear verdict
  per entry in one run (see `batchScreenList` below) - not offered by that competitor.
- **Coverage disclosures on every run, not just failures.** Every real-key run ends with a
  `run-coverage` record stating exactly what was checked this run (which datasets, which NAICS
  filter, which exclusions filter, how many identifiers screened) - not just a flag when something
  broke. Combined with `run-status`/`batch-screen-status` records on any truncation, a result is
  never silently partial. This is the structural fix for exactly the kind of thin/wrong-looking
  output that tends to earn a 1-star review.

The tradeoff is the official API's real limitation: **10 requests/day (unregistered), 1,000/day
(registered non-federal), 10,000/day (federal system account)**, tracked per API key. This actor
does not and cannot bypass that - it is not a "workaround." Its value is making the sanctioned,
rate-limited API easy to run well: bundled opportunities + exclusions in one normalized schema,
NAICS-code filtering, incremental/delta monitoring that spends your quota only on what's new since
the last run, and honest failure/coverage reporting instead of silent partial results.

### 2. Why this matters for contract-award and subcontracting compliance

Federal Acquisition Regulation 9.405 requires contracting officers to check SAM.gov exclusions
before soliciting offers, awarding a contract, or consenting to a subcontract, absent a
compelling-reason determination - and to check again immediately before award. FAR 9.405-2(b) and
the FAR 52.209-6 clause extend a parallel duty to prime contractors: before subcontracting over
$45,000 (commercial off-the-shelf items are exempt), a prime must verify the subcontractor is not
excluded and must notify the contracting officer in writing before proceeding if it is. This
actor's `batchScreenList` mode (below) is built specifically for that prime-contractor-side check
- submit your subcontractor list, get a matched/clear verdict per entry.

### 3. How it works

1. **You bring your own SAM.gov API key** (`samGovApiKey` input). Register a free SAM.gov
   account and request a public API key from the Account Details page - no cost, no KYC, not
   affiliated with Apify. Each run consumes quota from *your* key only; this actor never pools
   multiple customers' usage behind a shared key, so your quota isn't affected by other users.
   **Run it without a key first** to see how it works: a keyless run succeeds and returns one
   informational record explaining what a real key unlocks, at no charge - no real SAM.gov query
   is made until you add one.
2. Fetches **Opportunities** (`GET /opportunities/v2/search`) and/or **Exclusions**
   (`GET /entity-information/v4/exclusions`), paginated automatically.
3. Optional **NAICS code filtering**: the API accepts one NAICS code per request, so the actor
   issues one paginated request sequence per code you supply.
4. Optional **incremental monitor mode**: on scheduled runs, remembers the last successful
   checkpoint (via a persistent named key-value store) and only requests opportunities posted /
   exclusions updated since then, instead of a full re-pull every time.
5. A **quota guard** (`maxRecordsPerRun`) caps total records fetched in a single run so a
   misconfigured input can't blow through your whole daily API allowance in one run.
6. **Batch-screen a vendor/subcontractor list** (`batchScreenList` input): submit an array of
   `{ id?, ueiSAM?, cageCode?, entityName? }` entries and get one `batch-screen-result` record back
   per entry - a real match (with the full matched exclusion record attached), a clean/no-match
   result, or an error (e.g. no identifier supplied). `ueiSAM`/`cageCode` are precise lookups;
   `entityName` is a fuzzy name search, flagged as such in the output via `searchMethod`. Runs
   independently of `datasets`/`monitorMode` - a targeted per-entity check, not a bulk pull - and
   shares this run's `maxRequestsPerRun` budget with the opportunities/exclusions datasets (same
   daily SAM.gov quota). If the quota runs out mid-batch, a `batch-screen-status` record names
   exactly which entries were NOT screened - never mistake a missing result for a cleared one.

### 4. Input

| Field | Type | Notes |
|---|---|---|
| `samGovApiKey` | string (secret) | Bring your own SAM.gov API key - see How it works above. Optional: without one, the run succeeds and returns a single free `api-key-required` explainer record instead of querying SAM.gov |
| `datasets` | array | `"opportunities"`, `"exclusions"`, or both - default both |
| `naicsCodes` | array | One request sequence issued per code |
| `opportunityKeyword`, `placeOfPerformanceState`, `setAsideType` | | Opportunity-search filters, passed through to the API |
| `postedFrom` / `postedTo` | string | `MM/DD/YYYY` - required unless `monitorMode` is on; capped at a 1-year range by the API itself |
| `exclusionSearch` | object | Filters for the exclusions dataset (e.g. `exclusionType`) |
| `batchScreenList` | array | `{ id?, ueiSAM?, cageCode?, entityName? }` entries - see How it works above |
| `monitorMode` | boolean | Persistent incremental "new since last checkpoint" tracking |
| `maxRecordsPerRun` | integer | Caps total records fetched across `datasets` |
| `maxRequestsPerRun` | integer | Shared request budget across opportunities/exclusions/batch-screen - protects your daily SAM.gov quota from a misconfigured run |

See the Input tab for the full form with inline descriptions.

### 5. Output fields

Each dataset item has a `recordType` of `"opportunity"`, `"exclusion"`, `"run-status"` (emitted
only when a dataset's fetch was truncated or skipped this run), `"batch-screen-result"` (one per
`batchScreenList` entry), `"batch-screen-status"` (emitted only when the batch screen itself
was truncated by the request budget), `"api-key-required"` (emitted instead of any real data,
only on a run with no `samGovApiKey` - see below), or `"run-coverage"` (emitted once at the end
of every real-key run, stating what was actually covered - see below).

**`opportunity`**

| Field | Type | Notes |
|---|---|---|
| `noticeId`, `title`, `solicitationNumber` | string \| null | SAM.gov's own identifiers |
| `noticeType`, `baseType` | string \| null | e.g. `"Sources Sought"` |
| `active` | string \| null | `"Yes"`/`"No"`, SAM.gov's own string encoding, not a boolean |
| `postedDate`, `responseDeadline`, `archiveDate` | string \| null | |
| `naicsCode`, `classificationCode` | string \| null | |
| `setAsideType`, `setAsideDescription` | string \| null | e.g. `"SBA"` / `"Total Small Business Set-Aside (FAR 19.5)"` |
| `organizationPath`, `organizationCode` | string \| null | Full agency hierarchy, e.g. `"DEPT OF DEFENSE.DEPT OF THE ARMY.AMC..."` |
| `placeOfPerformance` | object \| null | Nested address object, passed through as SAM.gov provides it |
| `pointOfContact` | array \| null | Nested contact objects |
| `award` | object \| null | Populated only for award-notice types |
| `descriptionLink`, `additionalInfoLink`, `uiLink`, `resourceLinks` | string/array \| null | `uiLink` is the human-readable SAM.gov page; `descriptionLink` is a further API call, not fetched by this actor |
| `fetchedAt`, `sourceApi` | | `sourceApi` is `"api.sam.gov/opportunities/v2/search"` |

**`exclusion`**

| Field | Type | Notes |
|---|---|---|
| `ueiSAM`, `cageCode` | string \| null | Present for entity exclusions, `null` for most individual exclusions |
| `entityName`, `individualName` | string \| null | For an individual exclusion, both are populated with the same name |
| `classificationType` | string \| null | `"Individual"` or an entity classification |
| `exclusionType`, `exclusionProgram` | string \| null | e.g. `"Ineligible (Proceedings Complete)"` / `"Reciprocal"` |
| `excludingAgencyName`, `excludingAgencyCode` | string \| null | |
| `activateDate`, `terminationDate`, `terminationType`, `recordStatus` | string \| null | `terminationType: "Definite"` pairs with a real `terminationDate`; some exclusions are indefinite |
| `createDate`, `updateDate` | string \| null | |
| `city`, `stateOrProvinceCode`, `countryCode` | string \| null | |
| `fetchedAt`, `sourceApi` | | `sourceApi` is `"api.sam.gov/entity-information/v4/exclusions"` |

**`batch-screen-result`** (one per `batchScreenList` entry, always)

| Field | Type | Notes |
|---|---|---|
| `inputId` | string \| null | Echoes your entry's `id`, or an auto-assigned `"#0"`-style index if omitted |
| `queriedUeiSAM`, `queriedCageCode`, `queriedEntityName` | string \| null | Echoes whichever identifier(s) you submitted |
| `searchMethod` | string \| null | `"ueiSAM"`, `"cageCode"`, or `"entityName"` - `entityName` is a fuzzy search, flagged as such here |
| `matched` | boolean \| null | `null` only when `error` is set (the entry couldn't be checked at all) |
| `matchCount` | number \| null | |
| `matches` | array \| null | Full matched `exclusion` record(s) attached when `matched: true` |
| `error` | string \| null | e.g. no identifier supplied - this entry was NOT checked |
| `checkedAt` | string (ISO datetime) | |

**Status records** (`run-status`, `batch-screen-status`) each carry a `message` explaining exactly
what was cut short and why - read it before treating an absence from the output as a clean result.

**`api-key-required`** (only when `samGovApiKey` is absent)

| Field | Type | Notes |
|---|---|---|
| `status` | string | `"NO_DATA_KEYLESS_RUN"` |
| `message` | string | Explains no SAM.gov query was made and how to get a free key |
| `detectedAt` | string (ISO datetime) | |

This is the only record a keyless run produces - no `opportunity`/`exclusion`/`batch-screen-*`
records are ever mixed in. The run still exits `SUCCEEDED` and is not charged; this is intentional
(see "Running it without a key" below), not an error state.

**`run-coverage`** (once per real-key run, always - free, never charged)

| Field | Type | Notes |
|---|---|---|
| `datasetsRequested` | array | Which of `"opportunities"`/`"exclusions"` this run actually requested |
| `naicsCodesFilter` | array \| null | NAICS codes this run filtered opportunities by, or `null` if unfiltered (all codes) |
| `opportunitiesWindow` | object \| null | The `postedFrom`/`postedTo` window actually used, or `null` if opportunities wasn't requested |
| `exclusionsFilterDescription` | string \| null | Human-readable description of the exclusions filter actually applied (or "no filter - full active exclusions list...") |
| `batchScreenCount` | number | How many identifiers were submitted for batch screening |
| `monitorMode` | boolean | Whether this was an incremental monitor-mode run |
| `message` | string | Prose summary of the above |
| `detectedAt` | string (ISO datetime) | |

This is a **scope** statement, not a result - it says what was *attempted*, not whether it
*completed*. Read it alongside any `run-status`/`batch-screen-status` records to know both what
this run covered and whether that coverage was cut short. Unlike the failure-only `run-status`/
`batch-screen-status` records, `run-coverage` is emitted on every real-key run, including a
completely clean one - so a result set is never ambiguous about its own scope.

#### Real sample output

Live-fetched (NAICS `541511`, January 2026) - not a fabricated example:

```json
{
  "recordType": "opportunity",
  "noticeId": "6f6b4d02f80441d4a59781e40d1621c1",
  "title": "Construction Cost Estimating Software Licenses ",
  "solicitationNumber": "W911S226QBOOK",
  "noticeType": "Sources Sought",
  "baseType": "Sources Sought",
  "active": "Yes",
  "postedDate": "2026-01-06",
  "responseDeadline": "2026-01-16T10:00:00-05:00",
  "archiveDate": "2027-01-06",
  "naicsCode": "541511",
  "classificationCode": "7A21",
  "setAsideType": "SBA",
  "setAsideDescription": "Total Small Business Set-Aside (FAR 19.5)",
  "organizationPath": "DEPT OF DEFENSE.DEPT OF THE ARMY.AMC.ACC.MISSION INSTALLATION CONTRACTING COMMAND.419TH CSB.W6QM MICC-FT DRUM",
  "organizationCode": "021.2100.AMC.ACC.MICC.419TH CSB.W911S2",
  "placeOfPerformance": { "streetAddress": "", "zip": "13602" },
  "pointOfContact": [{ "fax": "", "type": "primary", "email": "cody.a.bresette.civ@army.mil", "phone": "3157723397", "title": null, "fullName": "Cody Bresette" }],
  "award": null,
  "descriptionLink": "https://api.sam.gov/prod/opportunities/v1/noticedesc?noticeid=6f6b4d02f80441d4a59781e40d1621c1",
  "additionalInfoLink": null,
  "uiLink": "https://sam.gov/workspace/contract/opp/6f6b4d02f80441d4a59781e40d1621c1/view",
  "resourceLinks": null,
  "fetchedAt": "2026-07-18T01:40:08.487Z",
  "sourceApi": "api.sam.gov/opportunities/v2/search"
}
````

```json
{
  "recordType": "exclusion",
  "ueiSAM": null,
  "cageCode": null,
  "entityName": "John Billy Donnahoo",
  "individualName": "John Billy Donnahoo",
  "classificationType": "Individual",
  "exclusionType": "Ineligible (Proceedings Complete)",
  "exclusionProgram": "Reciprocal",
  "excludingAgencyName": "JUSTICE, DEPARTMENT OF",
  "excludingAgencyCode": "DOJ",
  "activateDate": "03-31-2026",
  "terminationDate": "03-31-2227",
  "terminationType": "Definite",
  "recordStatus": "Active",
  "createDate": "05-05-2026",
  "updateDate": "05-05-2026",
  "city": "McMinnville",
  "stateOrProvinceCode": "OR",
  "countryCode": "USA",
  "fetchedAt": "2026-07-18T01:41:14.302Z",
  "sourceApi": "api.sam.gov/entity-information/v4/exclusions"
}
```

```json
{
  "recordType": "batch-screen-result",
  "inputId": "#0",
  "queriedUeiSAM": null,
  "queriedCageCode": null,
  "queriedEntityName": "nonexistent test vendor xyz",
  "searchMethod": "entityName",
  "matched": false,
  "matchCount": 0,
  "matches": [],
  "error": null,
  "checkedAt": "2026-07-18T01:40:44.667Z"
}
```

A `matched: false`, zero-match clean result is the honest, expected shape for a real vendor with
no exclusions on file - the same "confirmed clean, not just absent" principle this fleet's other
screening actors apply.

### 6. Use cases

- **Contracting officers & acquisition teams** - check SAM.gov exclusions before soliciting,
  awarding, or consenting to a subcontract, per FAR 9.405.
- **Prime contractors** - `batchScreenList` your subcontractor roster before award, satisfying the
  FAR 52.209-6 written-notification duty for subcontracts over $45,000.
- **GovCon business development** - monitor NAICS-filtered opportunity feeds for new solicitations
  in your capability area, with `monitorMode` delivering only what's new since your last check.
- **Compliance-SaaS platforms** - embed federal opportunity + exclusion data into an existing
  GovCon workflow tool via one normalized feed instead of two separate API integrations.
- **Legal / due diligence** - screen an acquisition target's own federal contracting exposure and
  exclusion history before closing.

### 7. Setting up monitoring alerts (Apify Schedules + your own notification channel)

This actor produces structured data; wiring that into an email/Slack/Teams alert is done with
Apify's own platform features, not actor-specific code:

1. **Create an Apify Schedule** pointing at this actor, with `monitorMode: true` and your
   `naicsCodes`/`datasets` set. Daily is typical, and respects your API key's daily quota better
   than more frequent polling.
2. **Attach a Webhook** to the schedule/actor for the `ACTOR.RUN.SUCCEEDED` event. Apify's
   webhook payload includes the run's dataset ID, so your webhook target can fetch the new items.
3. **Route the webhook** to Slack/Teams via their native "incoming webhook" URL, or to email via
   a lightweight relay (e.g. a Zapier/Make webhook-to-email step) - since `monitorMode` only
   delivers genuinely new opportunities/exclusions since the last checkpoint, every triggered
   notification represents real new information.
4. Because the dataset always distinguishes `opportunity`/`exclusion` records from `run-status`
   status records, your alert logic can (and should) treat a `TRUNCATED`/`SKIPPED` status as
   "monitoring degraded this run" - e.g. your daily API quota ran out - rather than silently
   trusting a thin result as complete.

### 8. Pricing

Pay per event (current live pricing - confirmed via the Apify API at time of writing):

- `opportunity-record` - **$0.004** per contract opportunity delivered
- `exclusion-record` - **$0.003** per exclusion (debarment/suspension) record delivered
- `batch-screen-record` - **$0.005** per identifier screened in `batchScreenList` mode (charged
  per identifier submitted, not per match found)
- `monitor-run` - **$0.05** per scheduled monitor-mode run, in addition to per-record charges

**No per-run start fee.** A run that finds nothing (a narrow filter, an empty batch result, a
keyless test run) costs nothing beyond what it actually returns - unlike at least one other
SAM.gov actor in the Store, which charges a flat fee the moment any run starts regardless of
what it finds.

See the actor's Pricing tab for the authoritative, current numbers.

### 9. Use from Claude, Cursor, and other AI agents (MCP)

This actor can be called as a tool by any MCP-compatible AI client (Claude Desktop, Cursor,
VS Code, etc.) via Apify's hosted MCP server, without any actor-specific integration code:

1. Point your MCP client at `https://mcp.apify.com`, authenticated with your Apify API token
   (as a bearer header or via OAuth - see Apify's MCP docs for client-specific config).
2. Expose this actor specifically with the `tools` query parameter:
   `https://mcp.apify.com?tools=mooseandraven/samgov-federal-contracting-suite`
3. Your agent can then call it directly - e.g. "check if Acme Corp is excluded from federal
   contracting" or "find open Sources Sought notices under NAICS 541511" maps naturally onto
   `batchScreenList` or `naicsCodes`/`opportunityKeyword`. You'll need your own SAM.gov API key
   either way (see How it works above) - this actor doesn't provide one. The actor's own input
   schema (this page's Input tab) is what the MCP tool schema is built from, so any filter
   documented here is available to the agent.

### 10. FAQ

**Do I need a SAM.gov API key?** For real data, yes - this is the one actor in this fleet that
requires bringing your own key. Register a free SAM.gov account and request a Public API Key from
your Account Details page. No cost, no KYC beyond SAM.gov's own registration. You can run the
actor without one first: it succeeds and returns a single `api-key-required` record explaining
what a real key unlocks, at no charge - no SAM.gov query is made until you add one.

**Why does my key stop working after a while?** SAM.gov's own public API keys are time-limited
(~89 days, confirmed empirically) and must be periodically regenerated via
`sam.gov/profile/details` - this is SAM.gov's own key-expiry policy, not something this actor
controls. Treat a key-expiry error as a real, expected event to plan around, not a bug.

**My api.data.gov key doesn't work - why?** `api.data.gov` and `api.sam.gov` are separate key
systems (confirmed empirically) - an api.data.gov-issued key will not authenticate against
`api.sam.gov`. You need a key issued directly from your SAM.gov profile.

**What's the pricing/subscription model?** Pay-per-event, not a flat subscription - see Pricing
above. Note your own SAM.gov API quota (10/1,000/10,000 requests per day depending on your
account tier) is a separate, real constraint this actor respects but cannot raise.

**Why do I sometimes get a `run-status` or `batch-screen-status` record instead of just data?**
This actor deliberately surfaces its own limits instead of hiding them - see Output fields above.
A capped/skipped pull (often your own daily SAM.gov quota running out) is never silently reported
as complete.

### 11. What it does NOT do / Limitations

- **Wage determinations are NOT included.** SAM.gov publishes Davis-Bacon/SCA wage determinations
  only as individual web pages (e.g. `sam.gov/wage-determination/{id}/{revision}`) with no official
  API or bulk-download endpoint. Building that would require scraping the website, which SAM.gov's
  Terms of Use prohibit. This is a deliberate scope cut, not an oversight.
- **The Exclusions API's synchronous JSON endpoint pages at 10 records/page, 10,000 records total.**
  Pulling a large exclusions result set burns quota fast (100 requests per 1,000 records). Narrow
  your `exclusionSearch` filters for anything beyond a small pull.
- **Date-range requests are capped at 1 year** by the API itself (`postedFrom`/`postedTo`), and
  `postedFrom`/`postedTo` are required unless `monitorMode` is on.
- **D\&B-sourced fields**: some exclusion identification fields are Dun & Bradstreet-derived, which
  SAM.gov's terms restrict from bulk redistribution outside authorized use. This actor surfaces
  those fields for the requesting user's own compliance/vetting use only.
- **This actor requires your own API key and is bound by YOUR key's daily quota** - it cannot
  pool quota across users or bypass SAM.gov's own rate limits, by design (see What makes this
  different above).

### Local development

```
npm install
npm test                # recorded-fixture unit tests, no API key needed
npx apify-cli run       # local end-to-end run - requires a real samGovApiKey in
                         # storage/key_value_stores/default/INPUT.json
```

### Support

Built and maintained by Moose & Raven. Questions or issues: support@mooseandraven.com.

# Actor input Schema

## `samGovApiKey` (type: `string`):

Get this from sam.gov/profile/details (Public API Key field, after signing in) - NOT from api.data.gov. An api.data.gov key looks similar but does NOT work here (confirmed directly: it fails with a 404/empty-body 'unrecognized key' error); they are separate systems. Registering an ENTITY (not just an individual account) raises your daily quota from 10/day to 1,000/day. Full tiers: 10/day unregistered-role, 1,000/day non-federal entity role, 10,000/day .gov/.mil system account. This actor calls the official api.sam.gov REST API only - it does not scrape SAM.gov's website - and never pools multiple customers behind one key. Leave this blank to see how the actor works first: a keyless run succeeds and returns one informational record explaining what a real key unlocks, at no charge.

## `datasets` (type: `array`):

Which SAM.gov datasets to pull this run. Wage determinations are NOT available: SAM.gov publishes them only as web pages with no official API, and scraping them would violate SAM.gov's Terms of Use (no bots/automated harvesting) — see README limitations.

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

One or more 6-digit NAICS codes. The official API accepts one NAICS code per request, so the actor issues one paginated request sequence per code (each consumes your daily quota). Leave empty to fetch all NAICS codes (not recommended — high quota burn).

## `opportunityKeyword` (type: `string`):

Optional keyword filter on opportunity title (maps to the API's 'title' parameter).

## `placeOfPerformanceState` (type: `string`):

Optional 2-letter US state code to filter opportunities by place of performance.

## `setAsideType` (type: `string`):

Optional SAM.gov set-aside type code filter (e.g. SBA, 8A, WOSB, SDVOSBC).

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

Start of the opportunity posted-date window. Required by the SAM.gov API if monitorMode is off. Max 1-year range. Ignored (and computed automatically) when monitorMode is on and prior run state exists.

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

End of the opportunity posted-date window. Required by the SAM.gov API if monitorMode is off. Max 1-year range.

## `exclusionSearch` (type: `object`):

Optional filters passed to the SAM.gov Exclusions API (classification, exclusionName, stateProvince, ueiSAM, cageCode, q, etc). Leave empty to fetch all active exclusions (10,000-record ceiling on the synchronous endpoint this actor uses).

## `batchScreenList` (type: `array`):

Check a list of prospective subcontractors against SAM.gov exclusions in one run — array-of-identifiers in, matched/clear flags out. Each entry: { id?: string, ueiSAM?: string, cageCode?: string, entityName?: string } — at least one of ueiSAM/cageCode/entityName is required per entry (ueiSAM and cageCode are precise lookups; entityName is a fuzzy name search, flagged as such in the output). Identifier fields should be strings (a bare number like an unquoted CAGE code will be coerced, but quoting it yourself avoids relying on that). FAR 9.405 requires contracting officers to check SAM.gov exclusions before award; FAR 9.405-2(b) and the FAR 52.209-6 clause extend a parallel duty to PRIME CONTRACTORS — before subcontracting over $45,000 (commercial-off-the-shelf items are exempt), a prime must verify the subcontractor is not excluded. Every entry you submit gets exactly one output record (a match, a clean result, or an error) — the run never silently skips an entry, even a malformed one; if the daily API quota runs out mid-batch, a loud run-status record names exactly which entries were NOT checked, so you never mistake an unscreened entry for a cleared one. Shares this run's request budget with the opportunities/exclusions datasets (same daily SAM.gov quota).

## `monitorMode` (type: `boolean`):

When on, the actor remembers the last successful run's cutoff (per Apify key-value store) and only requests opportunities posted / exclusions updated since then, instead of a full re-pull. Designed for scheduled runs. Charges one monitor-run PPE event in addition to per-record events.

## `maxRecordsPerRun` (type: `integer`):

Safety cap on records fetched, applied INDEPENDENTLY to opportunities and exclusions (not shared between them — a shared budget used to mean opportunities could silently starve exclusions of any processing at all). Set to 0 for no record cap (see maxRequestsPerRun for the real quota guard).

## `maxRequestsPerRun` (type: `integer`):

SAM.gov's actual daily quota is REQUEST-count-based (10/1,000/10,000 per day depending on your key tier), not record-count-based — exclusions page at only 10 records/request, so an unfiltered pull can burn hundreds of requests fast. This is a SHARED budget across opportunities + exclusions + batchScreenList in the same run (they all draw on the same daily quota). If this cap truncates a dataset or the batch screen, the actor emits a loud `run-status`/`batch-screen-status` TRUNCATED/SKIPPED record — it never reports a partial pull (or a partially-screened batch) as complete. Set to 0 for no request cap (not recommended unless you understand your key's daily limit).

## Actor input object example

```json
{
  "datasets": [
    "opportunities",
    "exclusions"
  ],
  "naicsCodes": [],
  "exclusionSearch": {},
  "batchScreenList": [],
  "monitorMode": false,
  "maxRecordsPerRun": 5000,
  "maxRequestsPerRun": 200
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("mooseandraven/samgov-federal-contracting-suite").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("mooseandraven/samgov-federal-contracting-suite").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call mooseandraven/samgov-federal-contracting-suite --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=mooseandraven/samgov-federal-contracting-suite",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "SAM.gov Contracts & Exclusions API - No Scraping, No Start Fee",
        "description": "Federal contract opportunities + exclusions via the official api.sam.gov REST API - not fragile site-scraping. FAR 52.209-6 batch subcontractor screening included (rare). No per-run start fee (others charge $0.10/run). Honest coverage disclosures - never a silent partial result.",
        "version": "0.1",
        "x-build-id": "G9HqSUHs6BLK82Onb"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/mooseandraven~samgov-federal-contracting-suite/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-mooseandraven-samgov-federal-contracting-suite",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/mooseandraven~samgov-federal-contracting-suite/runs": {
            "post": {
                "operationId": "runs-sync-mooseandraven-samgov-federal-contracting-suite",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/mooseandraven~samgov-federal-contracting-suite/run-sync": {
            "post": {
                "operationId": "run-sync-mooseandraven-samgov-federal-contracting-suite",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "samGovApiKey": {
                        "title": "SAM.gov API key",
                        "type": "string",
                        "description": "Get this from sam.gov/profile/details (Public API Key field, after signing in) - NOT from api.data.gov. An api.data.gov key looks similar but does NOT work here (confirmed directly: it fails with a 404/empty-body 'unrecognized key' error); they are separate systems. Registering an ENTITY (not just an individual account) raises your daily quota from 10/day to 1,000/day. Full tiers: 10/day unregistered-role, 1,000/day non-federal entity role, 10,000/day .gov/.mil system account. This actor calls the official api.sam.gov REST API only - it does not scrape SAM.gov's website - and never pools multiple customers behind one key. Leave this blank to see how the actor works first: a keyless run succeeds and returns one informational record explaining what a real key unlocks, at no charge."
                    },
                    "datasets": {
                        "title": "Datasets to fetch",
                        "type": "array",
                        "description": "Which SAM.gov datasets to pull this run. Wage determinations are NOT available: SAM.gov publishes them only as web pages with no official API, and scraping them would violate SAM.gov's Terms of Use (no bots/automated harvesting) — see README limitations.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "opportunities",
                                "exclusions"
                            ]
                        },
                        "default": [
                            "opportunities",
                            "exclusions"
                        ]
                    },
                    "naicsCodes": {
                        "title": "NAICS codes (opportunities filter)",
                        "type": "array",
                        "description": "One or more 6-digit NAICS codes. The official API accepts one NAICS code per request, so the actor issues one paginated request sequence per code (each consumes your daily quota). Leave empty to fetch all NAICS codes (not recommended — high quota burn).",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "opportunityKeyword": {
                        "title": "Opportunity title keyword",
                        "type": "string",
                        "description": "Optional keyword filter on opportunity title (maps to the API's 'title' parameter)."
                    },
                    "placeOfPerformanceState": {
                        "title": "Place of performance state",
                        "type": "string",
                        "description": "Optional 2-letter US state code to filter opportunities by place of performance."
                    },
                    "setAsideType": {
                        "title": "Set-aside type code",
                        "type": "string",
                        "description": "Optional SAM.gov set-aside type code filter (e.g. SBA, 8A, WOSB, SDVOSBC)."
                    },
                    "postedFrom": {
                        "title": "Posted from (MM/dd/yyyy)",
                        "type": "string",
                        "description": "Start of the opportunity posted-date window. Required by the SAM.gov API if monitorMode is off. Max 1-year range. Ignored (and computed automatically) when monitorMode is on and prior run state exists."
                    },
                    "postedTo": {
                        "title": "Posted to (MM/dd/yyyy)",
                        "type": "string",
                        "description": "End of the opportunity posted-date window. Required by the SAM.gov API if monitorMode is off. Max 1-year range."
                    },
                    "exclusionSearch": {
                        "title": "Exclusions search filters",
                        "type": "object",
                        "description": "Optional filters passed to the SAM.gov Exclusions API (classification, exclusionName, stateProvince, ueiSAM, cageCode, q, etc). Leave empty to fetch all active exclusions (10,000-record ceiling on the synchronous endpoint this actor uses).",
                        "default": {}
                    },
                    "batchScreenList": {
                        "title": "Batch-screen a vendor/subcontractor list (FAR 52.209-6)",
                        "type": "array",
                        "description": "Check a list of prospective subcontractors against SAM.gov exclusions in one run — array-of-identifiers in, matched/clear flags out. Each entry: { id?: string, ueiSAM?: string, cageCode?: string, entityName?: string } — at least one of ueiSAM/cageCode/entityName is required per entry (ueiSAM and cageCode are precise lookups; entityName is a fuzzy name search, flagged as such in the output). Identifier fields should be strings (a bare number like an unquoted CAGE code will be coerced, but quoting it yourself avoids relying on that). FAR 9.405 requires contracting officers to check SAM.gov exclusions before award; FAR 9.405-2(b) and the FAR 52.209-6 clause extend a parallel duty to PRIME CONTRACTORS — before subcontracting over $45,000 (commercial-off-the-shelf items are exempt), a prime must verify the subcontractor is not excluded. Every entry you submit gets exactly one output record (a match, a clean result, or an error) — the run never silently skips an entry, even a malformed one; if the daily API quota runs out mid-batch, a loud run-status record names exactly which entries were NOT checked, so you never mistake an unscreened entry for a cleared one. Shares this run's request budget with the opportunities/exclusions datasets (same daily SAM.gov quota).",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "title": "Label",
                                    "type": "string",
                                    "description": "Optional caller-supplied label for this entry, used to match output records back to your own list."
                                },
                                "ueiSAM": {
                                    "title": "UEI",
                                    "type": "string",
                                    "description": "12-char Unique Entity Identifier — the most precise lookup."
                                },
                                "cageCode": {
                                    "title": "CAGE code",
                                    "type": "string",
                                    "description": "CAGE code — a precise lookup."
                                },
                                "entityName": {
                                    "title": "Entity/individual name",
                                    "type": "string",
                                    "description": "Business or individual name — a fuzzy search, flagged as such in the output."
                                }
                            }
                        },
                        "default": []
                    },
                    "monitorMode": {
                        "title": "Incremental monitor mode",
                        "type": "boolean",
                        "description": "When on, the actor remembers the last successful run's cutoff (per Apify key-value store) and only requests opportunities posted / exclusions updated since then, instead of a full re-pull. Designed for scheduled runs. Charges one monitor-run PPE event in addition to per-record events.",
                        "default": false
                    },
                    "maxRecordsPerRun": {
                        "title": "Max records per dataset (quota guard)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Safety cap on records fetched, applied INDEPENDENTLY to opportunities and exclusions (not shared between them — a shared budget used to mean opportunities could silently starve exclusions of any processing at all). Set to 0 for no record cap (see maxRequestsPerRun for the real quota guard).",
                        "default": 5000
                    },
                    "maxRequestsPerRun": {
                        "title": "Max SAM.gov API requests per run (the real quota guard)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "SAM.gov's actual daily quota is REQUEST-count-based (10/1,000/10,000 per day depending on your key tier), not record-count-based — exclusions page at only 10 records/request, so an unfiltered pull can burn hundreds of requests fast. This is a SHARED budget across opportunities + exclusions + batchScreenList in the same run (they all draw on the same daily quota). If this cap truncates a dataset or the batch screen, the actor emits a loud `run-status`/`batch-screen-status` TRUNCATED/SKIPPED record — it never reports a partial pull (or a partially-screened batch) as complete. Set to 0 for no request cap (not recommended unless you understand your key's daily limit).",
                        "default": 200
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
